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 "ChildIterator.h"
12 #include "nsIInterfaceRequestorUtils.h"
13 #include "nsGkAtoms.h"
14 #include "nsGlobalWindow.h"
15 #include "nsContentUtils.h"
16 #include "ContentParent.h"
17 #include "nsPIDOMWindow.h"
18 #include "nsIDocShell.h"
19 #include "nsIDocShellTreeOwner.h"
20 #include "nsIFormControl.h"
21 #include "nsLayoutUtils.h"
22 #include "nsFrameTraversal.h"
23 #include "nsIWebNavigation.h"
25 #include "nsIBaseWindow.h"
26 #include "nsIAppWindow.h"
27 #include "nsTextControlFrame.h"
28 #include "nsViewManager.h"
29 #include "nsFrameSelection.h"
30 #include "mozilla/dom/Document.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/Element.h"
50 #include "mozilla/dom/ElementBinding.h"
51 #include "mozilla/dom/HTMLImageElement.h"
52 #include "mozilla/dom/HTMLInputElement.h"
53 #include "mozilla/dom/HTMLSlotElement.h"
54 #include "mozilla/dom/BrowserBridgeChild.h"
55 #include "mozilla/dom/Text.h"
56 #include "mozilla/dom/WindowGlobalParent.h"
57 #include "mozilla/dom/WindowGlobalChild.h"
58 #include "mozilla/EventDispatcher.h"
59 #include "mozilla/EventStateManager.h"
60 #include "mozilla/EventStates.h"
61 #include "mozilla/HTMLEditor.h"
62 #include "mozilla/IMEStateManager.h"
63 #include "mozilla/LookAndFeel.h"
64 #include "mozilla/PointerLockManager.h"
65 #include "mozilla/Preferences.h"
66 #include "mozilla/PresShell.h"
67 #include "mozilla/Services.h"
68 #include "mozilla/Unused.h"
69 #include "mozilla/StaticPrefs_full_screen_api.h"
73 # include "nsIDOMXULMenuListElement.h"
77 # include "nsAccessibilityService.h"
80 using namespace mozilla
;
81 using namespace mozilla::dom
;
82 using namespace mozilla::widget
;
84 // Two types of focus pr logging are available:
85 // 'Focus' for normal focus manager calls
86 // 'FocusNavigation' for tab and document navigation
87 LazyLogModule
gFocusLog("Focus");
88 LazyLogModule
gFocusNavigationLog("FocusNavigation");
90 #define LOGFOCUS(args) MOZ_LOG(gFocusLog, mozilla::LogLevel::Debug, args)
91 #define LOGFOCUSNAVIGATION(args) \
92 MOZ_LOG(gFocusNavigationLog, mozilla::LogLevel::Debug, args)
94 #define LOGTAG(log, format, content) \
95 if (MOZ_LOG_TEST(log, LogLevel::Debug)) { \
96 nsAutoCString tag("(none)"_ns); \
98 content->NodeInfo()->NameAtom()->ToUTF8String(tag); \
100 MOZ_LOG(log, LogLevel::Debug, (format, tag.get())); \
103 #define LOGCONTENT(format, content) LOGTAG(gFocusLog, format, content)
104 #define LOGCONTENTNAVIGATION(format, content) \
105 LOGTAG(gFocusNavigationLog, format, content)
107 struct nsDelayedBlurOrFocusEvent
{
108 nsDelayedBlurOrFocusEvent(EventMessage aEventMessage
, PresShell
* aPresShell
,
109 Document
* aDocument
, EventTarget
* aTarget
,
110 EventTarget
* aRelatedTarget
)
111 : mPresShell(aPresShell
),
112 mDocument(aDocument
),
114 mEventMessage(aEventMessage
),
115 mRelatedTarget(aRelatedTarget
) {}
117 nsDelayedBlurOrFocusEvent(const nsDelayedBlurOrFocusEvent
& aOther
)
118 : mPresShell(aOther
.mPresShell
),
119 mDocument(aOther
.mDocument
),
120 mTarget(aOther
.mTarget
),
121 mEventMessage(aOther
.mEventMessage
) {}
123 RefPtr
<PresShell
> mPresShell
;
124 nsCOMPtr
<Document
> mDocument
;
125 nsCOMPtr
<EventTarget
> mTarget
;
126 EventMessage mEventMessage
;
127 nsCOMPtr
<EventTarget
> mRelatedTarget
;
130 inline void ImplCycleCollectionUnlink(nsDelayedBlurOrFocusEvent
& aField
) {
131 aField
.mPresShell
= nullptr;
132 aField
.mDocument
= nullptr;
133 aField
.mTarget
= nullptr;
134 aField
.mRelatedTarget
= nullptr;
137 inline void ImplCycleCollectionTraverse(
138 nsCycleCollectionTraversalCallback
& aCallback
,
139 nsDelayedBlurOrFocusEvent
& aField
, const char* aName
, uint32_t aFlags
= 0) {
140 CycleCollectionNoteChild(
141 aCallback
, static_cast<nsIDocumentObserver
*>(aField
.mPresShell
.get()),
143 CycleCollectionNoteChild(aCallback
, aField
.mDocument
.get(), aName
, aFlags
);
144 CycleCollectionNoteChild(aCallback
, aField
.mTarget
.get(), aName
, aFlags
);
145 CycleCollectionNoteChild(aCallback
, aField
.mRelatedTarget
.get(), aName
,
149 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFocusManager
)
150 NS_INTERFACE_MAP_ENTRY(nsIFocusManager
)
151 NS_INTERFACE_MAP_ENTRY(nsIObserver
)
152 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
153 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIFocusManager
)
156 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFocusManager
)
157 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFocusManager
)
159 NS_IMPL_CYCLE_COLLECTION_WEAK(nsFocusManager
, mActiveWindow
,
160 mActiveBrowsingContextInContent
,
161 mActiveBrowsingContextInChrome
, mFocusedWindow
,
162 mFocusedBrowsingContextInContent
,
163 mFocusedBrowsingContextInChrome
, mFocusedElement
,
164 mFirstBlurEvent
, mFirstFocusEvent
,
165 mWindowBeingLowered
, mDelayedBlurFocusEvents
)
167 StaticRefPtr
<nsFocusManager
> nsFocusManager::sInstance
;
168 bool nsFocusManager::sTestMode
= false;
169 uint64_t nsFocusManager::sFocusActionCounter
= 0;
171 static const char* kObservedPrefs
[] = {"accessibility.browsewithcaret",
172 "accessibility.tabfocus_applies_to_xul",
173 "focusmanager.testmode", nullptr};
175 nsFocusManager::nsFocusManager()
176 : mActionIdForActiveBrowsingContextInContent(0),
177 mActionIdForActiveBrowsingContextInChrome(0),
178 mActionIdForFocusedBrowsingContextInContent(0),
179 mActionIdForFocusedBrowsingContextInChrome(0),
180 mActiveBrowsingContextInContentSetFromOtherProcess(false),
181 mEventHandlingNeedsFlush(false) {}
183 nsFocusManager::~nsFocusManager() {
184 Preferences::UnregisterCallbacks(nsFocusManager::PrefChanged
, kObservedPrefs
,
187 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
189 obs
->RemoveObserver(this, "xpcom-shutdown");
194 nsresult
nsFocusManager::Init() {
195 sInstance
= new nsFocusManager();
197 nsIContent::sTabFocusModelAppliesToXUL
=
198 Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
199 nsIContent::sTabFocusModelAppliesToXUL
);
201 sTestMode
= Preferences::GetBool("focusmanager.testmode", false);
203 Preferences::RegisterCallbacks(nsFocusManager::PrefChanged
, kObservedPrefs
,
206 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
208 obs
->AddObserver(sInstance
, "xpcom-shutdown", true);
215 void nsFocusManager::Shutdown() { sInstance
= nullptr; }
218 void nsFocusManager::PrefChanged(const char* aPref
, void* aSelf
) {
219 static_cast<nsFocusManager
*>(aSelf
)->PrefChanged(aPref
);
222 void nsFocusManager::PrefChanged(const char* aPref
) {
223 nsDependentCString
pref(aPref
);
224 if (pref
.EqualsLiteral("accessibility.browsewithcaret")) {
225 UpdateCaretForCaretBrowsingMode();
226 } else if (pref
.EqualsLiteral("accessibility.tabfocus_applies_to_xul")) {
227 nsIContent::sTabFocusModelAppliesToXUL
=
228 Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
229 nsIContent::sTabFocusModelAppliesToXUL
);
230 } else if (pref
.EqualsLiteral("focusmanager.testmode")) {
231 sTestMode
= Preferences::GetBool("focusmanager.testmode", false);
236 nsFocusManager::Observe(nsISupports
* aSubject
, const char* aTopic
,
237 const char16_t
* aData
) {
238 if (!nsCRT::strcmp(aTopic
, "xpcom-shutdown")) {
239 mActiveWindow
= nullptr;
240 mActiveBrowsingContextInContent
= nullptr;
241 mActionIdForActiveBrowsingContextInContent
= 0;
242 mActionIdForFocusedBrowsingContextInContent
= 0;
243 mActiveBrowsingContextInChrome
= nullptr;
244 mActionIdForActiveBrowsingContextInChrome
= 0;
245 mActionIdForFocusedBrowsingContextInChrome
= 0;
246 mFocusedWindow
= nullptr;
247 mFocusedBrowsingContextInContent
= nullptr;
248 mFocusedBrowsingContextInChrome
= nullptr;
249 mFocusedElement
= nullptr;
250 mFirstBlurEvent
= nullptr;
251 mFirstFocusEvent
= nullptr;
252 mWindowBeingLowered
= nullptr;
253 mDelayedBlurFocusEvents
.Clear();
259 static bool ActionIdComparableAndLower(uint64_t aActionId
,
260 uint64_t aReference
) {
261 MOZ_ASSERT(aActionId
, "Uninitialized action id");
262 auto [actionProc
, actionId
] =
263 nsContentUtils::SplitProcessSpecificId(aActionId
);
264 auto [refProc
, refId
] = nsContentUtils::SplitProcessSpecificId(aReference
);
265 return actionProc
== refProc
&& actionId
< refId
;
268 // given a frame content node, retrieve the nsIDOMWindow displayed in it
269 static nsPIDOMWindowOuter
* GetContentWindow(nsIContent
* aContent
) {
270 Document
* doc
= aContent
->GetComposedDoc();
272 Document
* subdoc
= doc
->GetSubDocumentFor(aContent
);
274 return subdoc
->GetWindow();
281 bool nsFocusManager::IsFocused(nsIContent
* aContent
) {
282 if (!aContent
|| !mFocusedElement
) {
285 return aContent
== mFocusedElement
;
288 bool nsFocusManager::IsTestMode() { return sTestMode
; }
290 bool nsFocusManager::IsInActiveWindow(BrowsingContext
* aBC
) const {
291 RefPtr
<BrowsingContext
> top
= aBC
->Top();
292 if (XRE_IsParentProcess()) {
293 top
= top
->Canonical()->TopCrossChromeBoundary();
295 return IsSameOrAncestor(top
, GetActiveBrowsingContext());
298 // get the current window for the given content node
299 static nsPIDOMWindowOuter
* GetCurrentWindow(nsIContent
* aContent
) {
300 Document
* doc
= aContent
->GetComposedDoc();
301 return doc
? doc
->GetWindow() : nullptr;
305 Element
* nsFocusManager::GetFocusedDescendant(
306 nsPIDOMWindowOuter
* aWindow
, SearchRange aSearchRange
,
307 nsPIDOMWindowOuter
** aFocusedWindow
) {
308 NS_ENSURE_TRUE(aWindow
, nullptr);
310 *aFocusedWindow
= nullptr;
312 Element
* currentElement
= nullptr;
313 nsPIDOMWindowOuter
* window
= aWindow
;
315 *aFocusedWindow
= window
;
316 currentElement
= window
->GetFocusedElement();
317 if (!currentElement
|| aSearchRange
== eOnlyCurrentWindow
) {
321 window
= GetContentWindow(currentElement
);
326 if (aSearchRange
== eIncludeAllDescendants
) {
330 MOZ_ASSERT(aSearchRange
== eIncludeVisibleDescendants
);
332 // If the child window doesn't have PresShell, it means the window is
334 nsIDocShell
* docShell
= window
->GetDocShell();
338 if (!docShell
->GetPresShell()) {
343 NS_IF_ADDREF(*aFocusedWindow
);
345 return currentElement
;
349 Element
* nsFocusManager::GetRedirectedFocus(nsIContent
* aContent
) {
351 if (aContent
->IsXULElement()) {
352 nsCOMPtr
<nsIDOMXULMenuListElement
> menulist
=
353 aContent
->AsElement()->AsXULMenuList();
355 RefPtr
<Element
> inputField
;
356 menulist
->GetInputField(getter_AddRefs(inputField
));
366 InputContextAction::Cause
nsFocusManager::GetFocusMoveActionCause(
368 if (aFlags
& nsIFocusManager::FLAG_BYTOUCH
) {
369 return InputContextAction::CAUSE_TOUCH
;
370 } else if (aFlags
& nsIFocusManager::FLAG_BYMOUSE
) {
371 return InputContextAction::CAUSE_MOUSE
;
372 } else if (aFlags
& nsIFocusManager::FLAG_BYKEY
) {
373 return InputContextAction::CAUSE_KEY
;
374 } else if (aFlags
& nsIFocusManager::FLAG_BYLONGPRESS
) {
375 return InputContextAction::CAUSE_LONGPRESS
;
377 return InputContextAction::CAUSE_UNKNOWN
;
381 nsFocusManager::GetActiveWindow(mozIDOMWindowProxy
** aWindow
) {
382 MOZ_ASSERT(XRE_IsParentProcess(),
383 "Must not be called outside the parent process.");
384 NS_IF_ADDREF(*aWindow
= mActiveWindow
);
389 nsFocusManager::GetActiveBrowsingContext(BrowsingContext
** aBrowsingContext
) {
390 NS_IF_ADDREF(*aBrowsingContext
= GetActiveBrowsingContext());
394 void nsFocusManager::FocusWindow(nsPIDOMWindowOuter
* aWindow
,
395 CallerType aCallerType
) {
397 sInstance
->SetFocusedWindowWithCallerType(
398 aWindow
, aCallerType
, sInstance
->GenerateFocusActionId());
403 nsFocusManager::GetFocusedWindow(mozIDOMWindowProxy
** aFocusedWindow
) {
404 NS_IF_ADDREF(*aFocusedWindow
= mFocusedWindow
);
409 nsFocusManager::GetFocusedContentBrowsingContext(
410 BrowsingContext
** aBrowsingContext
) {
411 MOZ_DIAGNOSTIC_ASSERT(
412 XRE_IsParentProcess(),
413 "We only have use cases for this in the parent process");
414 NS_IF_ADDREF(*aBrowsingContext
= GetFocusedBrowsingContextInChrome());
418 nsresult
nsFocusManager::SetFocusedWindowWithCallerType(
419 mozIDOMWindowProxy
* aWindowToFocus
, CallerType aCallerType
,
420 uint64_t aActionId
) {
421 LOGFOCUS(("<<SetFocusedWindow begin>>"));
423 nsCOMPtr
<nsPIDOMWindowOuter
> windowToFocus
=
424 nsPIDOMWindowOuter::From(aWindowToFocus
);
425 NS_ENSURE_TRUE(windowToFocus
, NS_ERROR_FAILURE
);
427 nsCOMPtr
<Element
> frameElement
= windowToFocus
->GetFrameElementInternal();
429 // pass false for aFocusChanged so that the caret does not get updated
430 // and scrolling does not occur.
431 SetFocusInner(frameElement
, 0, false, true, aActionId
);
433 // this is a top-level window. If the window has a child frame focused,
434 // clear the focus. Otherwise, focus should already be in this frame, or
435 // already cleared. This ensures that focus will be in this frame and not
437 nsIContent
* content
= windowToFocus
->GetFocusedElement();
439 if (nsCOMPtr
<nsPIDOMWindowOuter
> childWindow
= GetContentWindow(content
))
440 ClearFocus(windowToFocus
);
444 nsCOMPtr
<nsPIDOMWindowOuter
> rootWindow
= windowToFocus
->GetPrivateRoot();
446 RaiseWindow(rootWindow
, aCallerType
, aActionId
);
449 LOGFOCUS(("<<SetFocusedWindow end>>"));
454 NS_IMETHODIMP
nsFocusManager::SetFocusedWindow(
455 mozIDOMWindowProxy
* aWindowToFocus
) {
456 return SetFocusedWindowWithCallerType(aWindowToFocus
, CallerType::System
,
457 GenerateFocusActionId());
461 nsFocusManager::GetFocusedElement(Element
** aFocusedElement
) {
462 RefPtr
<Element
> focusedElement
= mFocusedElement
;
463 focusedElement
.forget(aFocusedElement
);
468 nsFocusManager::GetLastFocusMethod(mozIDOMWindowProxy
* aWindow
,
469 uint32_t* aLastFocusMethod
) {
470 // the focus method is stored on the inner window
471 nsCOMPtr
<nsPIDOMWindowOuter
> window
;
473 window
= nsPIDOMWindowOuter::From(aWindow
);
476 window
= mFocusedWindow
;
479 *aLastFocusMethod
= window
? window
->GetFocusMethod() : 0;
481 NS_ASSERTION((*aLastFocusMethod
& FOCUSMETHOD_MASK
) == *aLastFocusMethod
,
482 "invalid focus method");
487 nsFocusManager::SetFocus(Element
* aElement
, uint32_t aFlags
) {
488 LOGFOCUS(("<<SetFocus begin>>"));
490 NS_ENSURE_ARG(aElement
);
492 SetFocusInner(aElement
, aFlags
, true, true, GenerateFocusActionId());
494 LOGFOCUS(("<<SetFocus end>>"));
500 nsFocusManager::ElementIsFocusable(Element
* aElement
, uint32_t aFlags
,
501 bool* aIsFocusable
) {
502 NS_ENSURE_TRUE(aElement
, NS_ERROR_INVALID_ARG
);
504 *aIsFocusable
= FlushAndCheckIfFocusable(aElement
, aFlags
) != nullptr;
509 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
510 nsFocusManager::MoveFocus(mozIDOMWindowProxy
* aWindow
, Element
* aStartElement
,
511 uint32_t aType
, uint32_t aFlags
, Element
** aElement
) {
514 LOGFOCUS(("<<MoveFocus begin Type: %d Flags: %x>>", aType
, aFlags
));
516 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
) && mFocusedWindow
) {
517 Document
* doc
= mFocusedWindow
->GetExtantDoc();
518 if (doc
&& doc
->GetDocumentURI()) {
519 LOGFOCUS((" Focused Window: %p %s", mFocusedWindow
.get(),
520 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
524 LOGCONTENT(" Current Focus: %s", mFocusedElement
.get());
526 // use FLAG_BYMOVEFOCUS when switching focus with MoveFocus unless one of
527 // the other focus methods is already set, or we're just moving to the root
528 // or caret position.
529 if (aType
!= MOVEFOCUS_ROOT
&& aType
!= MOVEFOCUS_CARET
&&
530 (aFlags
& FOCUSMETHOD_MASK
) == 0) {
531 aFlags
|= FLAG_BYMOVEFOCUS
;
534 nsCOMPtr
<nsPIDOMWindowOuter
> window
;
536 window
= GetCurrentWindow(aStartElement
);
538 window
= aWindow
? nsPIDOMWindowOuter::From(aWindow
) : mFocusedWindow
.get();
541 NS_ENSURE_TRUE(window
, NS_ERROR_FAILURE
);
543 bool noParentTraversal
= aFlags
& FLAG_NOPARENTFRAME
;
544 nsCOMPtr
<nsIContent
> newFocus
;
545 nsresult rv
= DetermineElementToMoveFocus(window
, aStartElement
, aType
,
546 noParentTraversal
, true,
547 getter_AddRefs(newFocus
));
548 if (rv
== NS_SUCCESS_DOM_NO_OPERATION
) {
552 NS_ENSURE_SUCCESS(rv
, rv
);
554 LOGCONTENTNAVIGATION("Element to be focused: %s", newFocus
.get());
556 if (newFocus
&& newFocus
->IsElement()) {
557 // for caret movement, pass false for the aFocusChanged argument,
558 // otherwise the caret will end up moving to the focus position. This
559 // would be a problem because the caret would move to the beginning of the
560 // focused link making it impossible to navigate the caret over a link.
561 SetFocusInner(MOZ_KnownLive(newFocus
->AsElement()), aFlags
,
562 aType
!= MOVEFOCUS_CARET
, true, GenerateFocusActionId());
563 *aElement
= do_AddRef(newFocus
->AsElement()).take();
564 } else if (aType
== MOVEFOCUS_ROOT
|| aType
== MOVEFOCUS_CARET
) {
565 // no content was found, so clear the focus for these two types.
569 LOGFOCUS(("<<MoveFocus end>>"));
575 nsFocusManager::ClearFocus(mozIDOMWindowProxy
* aWindow
) {
576 LOGFOCUS(("<<ClearFocus begin>>"));
578 // if the window to clear is the focused window or an ancestor of the
579 // focused window, then blur the existing focused content. Otherwise, the
580 // focus is somewhere else so just update the current node.
581 NS_ENSURE_TRUE(aWindow
, NS_ERROR_INVALID_ARG
);
582 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
584 if (IsSameOrAncestor(window
, GetFocusedBrowsingContext())) {
585 BrowsingContext
* bc
= window
->GetBrowsingContext();
586 bool isAncestor
= (GetFocusedBrowsingContext() != bc
);
587 uint64_t actionId
= GenerateFocusActionId();
588 if (Blur(bc
, nullptr, isAncestor
, true, actionId
)) {
589 // if we are clearing the focus on an ancestor of the focused window,
590 // the ancestor will become the new focused window, so focus it
592 Focus(window
, nullptr, 0, true, false, false, true, actionId
);
596 window
->SetFocusedElement(nullptr);
599 LOGFOCUS(("<<ClearFocus end>>"));
605 nsFocusManager::GetFocusedElementForWindow(mozIDOMWindowProxy
* aWindow
,
607 mozIDOMWindowProxy
** aFocusedWindow
,
608 Element
** aElement
) {
610 if (aFocusedWindow
) {
611 *aFocusedWindow
= nullptr;
614 NS_ENSURE_TRUE(aWindow
, NS_ERROR_INVALID_ARG
);
615 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
617 nsCOMPtr
<nsPIDOMWindowOuter
> focusedWindow
;
618 RefPtr
<Element
> focusedElement
=
619 GetFocusedDescendant(window
,
620 aDeep
? nsFocusManager::eIncludeAllDescendants
621 : nsFocusManager::eOnlyCurrentWindow
,
622 getter_AddRefs(focusedWindow
));
624 focusedElement
.forget(aElement
);
626 if (aFocusedWindow
) {
627 NS_IF_ADDREF(*aFocusedWindow
= focusedWindow
);
634 nsFocusManager::MoveCaretToFocus(mozIDOMWindowProxy
* aWindow
) {
635 nsCOMPtr
<nsIWebNavigation
> webnav
= do_GetInterface(aWindow
);
636 nsCOMPtr
<nsIDocShellTreeItem
> dsti
= do_QueryInterface(webnav
);
638 if (dsti
->ItemType() != nsIDocShellTreeItem::typeChrome
) {
639 nsCOMPtr
<nsIDocShell
> docShell
= do_QueryInterface(dsti
);
640 NS_ENSURE_TRUE(docShell
, NS_ERROR_FAILURE
);
642 // don't move the caret for editable documents
644 docShell
->GetEditable(&isEditable
);
649 RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
650 NS_ENSURE_TRUE(presShell
, NS_ERROR_FAILURE
);
652 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
653 nsCOMPtr
<nsIContent
> content
= window
->GetFocusedElement();
655 MoveCaretToFocus(presShell
, content
);
663 void nsFocusManager::WindowRaised(mozIDOMWindowProxy
* aWindow
,
664 uint64_t aActionId
) {
669 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
670 BrowsingContext
* bc
= window
->GetBrowsingContext();
672 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
)) {
673 LOGFOCUS(("Window %p Raised [Currently: %p %p]", aWindow
,
674 mActiveWindow
.get(), mFocusedWindow
.get()));
675 Document
* doc
= window
->GetExtantDoc();
676 if (doc
&& doc
->GetDocumentURI()) {
677 LOGFOCUS((" Raised Window: %p %s", aWindow
,
678 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
681 doc
= mActiveWindow
->GetExtantDoc();
682 if (doc
&& doc
->GetDocumentURI()) {
683 LOGFOCUS((" Active Window: %p %s", mActiveWindow
.get(),
684 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
689 if (XRE_IsParentProcess()) {
690 if (mActiveWindow
== window
) {
691 // The window is already active, so there is no need to focus anything,
692 // but make sure that the right widget is focused. This is a special case
693 // for Windows because when restoring a minimized window, a second
694 // activation will occur and the top-level widget could be focused instead
695 // of the child we want. We solve this by calling SetFocus to ensure that
696 // what the focus manager thinks should be the current widget is actually
698 EnsureCurrentWidgetFocused(CallerType::System
);
702 // lower the existing window, if any. This shouldn't happen usually.
704 WindowLowered(mActiveWindow
, aActionId
);
706 } else if (bc
->IsTop()) {
707 BrowsingContext
* active
= GetActiveBrowsingContext();
708 if (active
== bc
&& !mActiveBrowsingContextInContentSetFromOtherProcess
) {
709 // EnsureCurrentWidgetFocused() should not be necessary with
714 if (active
&& active
!= bc
) {
715 if (active
->IsInProcess()) {
716 WindowLowered(active
->GetDOMWindow(), aActionId
);
718 // No else, because trying to lower other-process windows
719 // from here can result in the BrowsingContext no longer
720 // existing in the parent process by the time it deserializes
725 nsCOMPtr
<nsIDocShellTreeItem
> docShellAsItem
= window
->GetDocShell();
726 // If there's no docShellAsItem, this window must have been closed,
727 // in that case there is no tree owner.
728 if (!docShellAsItem
) {
732 // set this as the active window
733 if (XRE_IsParentProcess()) {
734 mActiveWindow
= window
;
735 } else if (bc
->IsTop()) {
736 SetActiveBrowsingContextInContent(bc
, aActionId
);
739 // ensure that the window is enabled and visible
740 nsCOMPtr
<nsIDocShellTreeOwner
> treeOwner
;
741 docShellAsItem
->GetTreeOwner(getter_AddRefs(treeOwner
));
742 nsCOMPtr
<nsIBaseWindow
> baseWindow
= do_QueryInterface(treeOwner
);
744 bool isEnabled
= true;
745 if (NS_SUCCEEDED(baseWindow
->GetEnabled(&isEnabled
)) && !isEnabled
) {
749 baseWindow
->SetVisibility(true);
752 if (XRE_IsParentProcess()) {
753 // Unsetting top-level focus upon lowering was inhibited to accommodate
754 // ATOK, so we need to do it here.
755 BrowserParent::UnsetTopLevelWebFocusAll();
756 ActivateOrDeactivate(window
, true);
759 // retrieve the last focused element within the window that was raised
760 nsCOMPtr
<nsPIDOMWindowOuter
> currentWindow
;
761 RefPtr
<Element
> currentFocus
= GetFocusedDescendant(
762 window
, eIncludeAllDescendants
, getter_AddRefs(currentWindow
));
764 NS_ASSERTION(currentWindow
, "window raised with no window current");
765 if (!currentWindow
) {
769 nsCOMPtr
<nsIAppWindow
> appWin(do_GetInterface(baseWindow
));
770 // We use mFocusedWindow here is basically for the case that iframe navigate
771 // from a.com to b.com for example, so it ends up being loaded in a different
772 // process after Fission, but
773 // currentWindow->GetBrowsingContext() == GetFocusedBrowsingContext() would
774 // still be true because focused browsing context is synced, and we won't
775 // fire a focus event while focusing if we use it as condition.
776 Focus(currentWindow
, currentFocus
, 0, currentWindow
!= mFocusedWindow
, false,
777 appWin
!= nullptr, true, aActionId
);
780 void nsFocusManager::WindowLowered(mozIDOMWindowProxy
* aWindow
,
781 uint64_t aActionId
) {
786 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
788 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
)) {
789 LOGFOCUS(("Window %p Lowered [Currently: %p %p]", aWindow
,
790 mActiveWindow
.get(), mFocusedWindow
.get()));
791 Document
* doc
= window
->GetExtantDoc();
792 if (doc
&& doc
->GetDocumentURI()) {
793 LOGFOCUS((" Lowered Window: %s",
794 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
797 doc
= mActiveWindow
->GetExtantDoc();
798 if (doc
&& doc
->GetDocumentURI()) {
799 LOGFOCUS((" Active Window: %s",
800 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
805 if (XRE_IsParentProcess()) {
806 if (mActiveWindow
!= window
) {
810 BrowsingContext
* bc
= window
->GetBrowsingContext();
811 BrowsingContext
* active
= GetActiveBrowsingContext();
812 if (active
!= bc
->Top()) {
817 // clear the mouse capture as the active window has changed
818 PresShell::ReleaseCapturingContent();
820 // In addition, reset the drag state to ensure that we are no longer in
822 if (mFocusedWindow
) {
823 nsCOMPtr
<nsIDocShell
> docShell
= mFocusedWindow
->GetDocShell();
825 if (PresShell
* presShell
= docShell
->GetPresShell()) {
826 RefPtr
<nsFrameSelection
> frameSelection
= presShell
->FrameSelection();
827 frameSelection
->SetDragState(false);
832 if (XRE_IsParentProcess()) {
833 ActivateOrDeactivate(window
, false);
836 // keep track of the window being lowered, so that attempts to raise the
837 // window can be prevented until we return. Otherwise, focus can get into
839 mWindowBeingLowered
= window
;
840 if (XRE_IsParentProcess()) {
841 mActiveWindow
= nullptr;
843 BrowsingContext
* bc
= window
->GetBrowsingContext();
844 if (bc
== bc
->Top()) {
845 SetActiveBrowsingContextInContent(nullptr, aActionId
);
849 if (mFocusedWindow
) {
850 Blur(nullptr, nullptr, true, true, aActionId
);
853 mWindowBeingLowered
= nullptr;
856 nsresult
nsFocusManager::ContentRemoved(Document
* aDocument
,
857 nsIContent
* aContent
) {
858 NS_ENSURE_ARG(aDocument
);
859 NS_ENSURE_ARG(aContent
);
861 nsPIDOMWindowOuter
* window
= aDocument
->GetWindow();
866 // if the content is currently focused in the window, or is an
867 // shadow-including inclusive ancestor of the currently focused element,
868 // reset the focus within that window.
869 Element
* content
= window
->GetFocusedElement();
871 nsContentUtils::ContentIsHostIncludingDescendantOf(content
, aContent
)) {
872 window
->SetFocusedElement(nullptr);
874 // if this window is currently focused, clear the global focused
875 // element as well, but don't fire any events.
876 if (window
->GetBrowsingContext() == GetFocusedBrowsingContext()) {
877 mFocusedElement
= nullptr;
879 // Check if the node that was focused is an iframe or similar by looking
880 // if it has a subdocument. This would indicate that this focused iframe
881 // and its descendants will be going away. We will need to move the
882 // focus somewhere else, so just clear the focus in the toplevel window
883 // so that no element is focused.
885 // This check does not work correctly in Fission:
886 // https://bugzilla.mozilla.org/show_bug.cgi?id=1613054
887 Document
* subdoc
= aDocument
->GetSubDocumentFor(content
);
889 nsCOMPtr
<nsIDocShell
> docShell
= subdoc
->GetDocShell();
891 nsCOMPtr
<nsPIDOMWindowOuter
> childWindow
= docShell
->GetWindow();
893 IsSameOrAncestor(childWindow
, GetFocusedBrowsingContext())) {
894 if (XRE_IsParentProcess()) {
895 ClearFocus(mActiveWindow
);
897 BrowsingContext
* active
= GetActiveBrowsingContext();
899 if (active
->IsInProcess()) {
900 ClearFocus(active
->GetDOMWindow());
902 mozilla::dom::ContentChild
* contentChild
=
903 mozilla::dom::ContentChild::GetSingleton();
904 MOZ_ASSERT(contentChild
);
905 contentChild
->SendClearFocus(active
);
907 } // no else, because ClearFocus does nothing with nullptr
914 // Notify the editor in case we removed its ancestor limiter.
915 if (content
->IsEditable()) {
916 nsCOMPtr
<nsIDocShell
> docShell
= aDocument
->GetDocShell();
918 RefPtr
<HTMLEditor
> htmlEditor
= docShell
->GetHTMLEditor();
920 RefPtr
<Selection
> selection
= htmlEditor
->GetSelection();
921 if (selection
&& selection
->GetFrameSelection() &&
922 content
== selection
->GetFrameSelection()->GetAncestorLimiter()) {
923 htmlEditor
->FinalizeSelection();
929 NotifyFocusStateChange(content
, nullptr, 0, /* aGettingFocus = */ false,
936 void nsFocusManager::WindowShown(mozIDOMWindowProxy
* aWindow
,
942 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
944 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
)) {
945 LOGFOCUS(("Window %p Shown [Currently: %p %p]", window
.get(),
946 mActiveWindow
.get(), mFocusedWindow
.get()));
947 Document
* doc
= window
->GetExtantDoc();
948 if (doc
&& doc
->GetDocumentURI()) {
949 LOGFOCUS(("Shown Window: %s",
950 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
953 if (mFocusedWindow
) {
954 doc
= mFocusedWindow
->GetExtantDoc();
955 if (doc
&& doc
->GetDocumentURI()) {
956 LOGFOCUS((" Focused Window: %s",
957 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
962 if (XRE_IsParentProcess()) {
963 if (BrowsingContext
* bc
= window
->GetBrowsingContext()) {
965 bc
->SetIsActiveBrowserWindow(bc
->GetIsActiveBrowserWindow());
970 if (XRE_IsParentProcess()) {
971 if (mFocusedWindow
!= window
) {
975 BrowsingContext
* bc
= window
->GetBrowsingContext();
976 if (!bc
|| mFocusedBrowsingContextInContent
!= bc
) {
979 // Sync the window for a newly-created OOP iframe
980 // Set actionId to zero to signify that it should be ignored.
981 SetFocusedWindowInternal(window
, 0, false);
985 nsCOMPtr
<nsPIDOMWindowOuter
> currentWindow
;
986 RefPtr
<Element
> currentFocus
= GetFocusedDescendant(
987 window
, eIncludeAllDescendants
, getter_AddRefs(currentWindow
));
990 Focus(currentWindow
, currentFocus
, 0, true, false, false, true,
991 GenerateFocusActionId());
994 // Sometimes, an element in a window can be focused before the window is
995 // visible, which would mean that the widget may not be properly focused.
996 // When the window becomes visible, make sure the right widget is focused.
997 EnsureCurrentWidgetFocused(CallerType::System
);
1001 void nsFocusManager::WindowHidden(mozIDOMWindowProxy
* aWindow
,
1002 uint64_t aActionId
) {
1003 // if there is no window or it is not the same or an ancestor of the
1004 // currently focused window, just return, as the current focus will not
1011 nsCOMPtr
<nsPIDOMWindowOuter
> window
= nsPIDOMWindowOuter::From(aWindow
);
1013 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
)) {
1014 LOGFOCUS(("Window %p Hidden [Currently: %p %p]", window
.get(),
1015 mActiveWindow
.get(), mFocusedWindow
.get()));
1017 Document
* doc
= window
->GetExtantDoc();
1018 if (doc
&& doc
->GetDocumentURI()) {
1019 LOGFOCUS((" Hide Window: %s",
1020 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
1023 if (mFocusedWindow
) {
1024 doc
= mFocusedWindow
->GetExtantDoc();
1025 if (doc
&& doc
->GetDocumentURI()) {
1026 LOGFOCUS((" Focused Window: %s",
1027 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
1031 if (mActiveWindow
) {
1032 doc
= mActiveWindow
->GetExtantDoc();
1033 if (doc
&& doc
->GetDocumentURI()) {
1034 LOGFOCUS((" Active Window: %s",
1035 doc
->GetDocumentURI()->GetSpecOrDefault().get()));
1040 if (!IsSameOrAncestor(window
, mFocusedWindow
)) {
1044 // at this point, we know that the window being hidden is either the focused
1045 // window, or an ancestor of the focused window. Either way, the focus is no
1046 // longer valid, so it needs to be updated.
1048 RefPtr
<Element
> oldFocusedElement
= std::move(mFocusedElement
);
1050 nsCOMPtr
<nsIDocShell
> focusedDocShell
= mFocusedWindow
->GetDocShell();
1051 if (!focusedDocShell
) {
1055 RefPtr
<PresShell
> presShell
= focusedDocShell
->GetPresShell();
1057 if (oldFocusedElement
&& oldFocusedElement
->IsInComposedDoc()) {
1058 NotifyFocusStateChange(oldFocusedElement
, nullptr, 0, false, false);
1059 window
->UpdateCommands(u
"focus"_ns
, nullptr, 0);
1062 SendFocusOrBlurEvent(eBlur
, presShell
,
1063 oldFocusedElement
->GetComposedDoc(),
1064 oldFocusedElement
, 1, false);
1068 nsPresContext
* focusedPresContext
=
1069 presShell
? presShell
->GetPresContext() : nullptr;
1070 IMEStateManager::OnChangeFocus(focusedPresContext
, nullptr,
1071 GetFocusMoveActionCause(0));
1073 SetCaretVisible(presShell
, false, nullptr);
1076 // If a window is being "hidden" because its BrowsingContext is changing
1077 // remoteness, we don't want to handle docshell destruction by moving focus.
1078 // Instead, the focused browsing context should stay the way it is (so that
1079 // the newly "shown" window in the other process knows to take focus) and
1080 // we should just null out the process-local field.
1081 nsCOMPtr
<nsIDocShell
> docShellBeingHidden
= window
->GetDocShell();
1082 // Check if we're currently hiding a non-remote nsDocShell due to its
1083 // BrowsingContext navigating to become remote. Normally, when a focused
1084 // subframe is hidden, focus is moved to the frame element, but focus should
1085 // stay with the BrowsingContext when performing a process switch. We don't
1086 // need to consider process switches where the hiding docshell is already
1087 // remote (ie. GetEmbedderElement is nullptr), as shifting remoteness to the
1088 // frame element is handled elsewhere.
1089 if (nsDocShell::Cast(docShellBeingHidden
)->WillChangeProcess() &&
1090 docShellBeingHidden
->GetBrowsingContext()->GetEmbedderElement()) {
1091 if (mFocusedWindow
!= window
) {
1092 // The window being hidden is an ancestor of the focused window.
1094 BrowsingContext
* ancestor
= window
->GetBrowsingContext();
1095 BrowsingContext
* bc
= mFocusedWindow
->GetBrowsingContext();
1098 MOZ_ASSERT(false, "Should have found ancestor");
1100 bc
= bc
->GetParent();
1101 if (ancestor
== bc
) {
1106 // This call adjusts the focused browsing context and window.
1107 // The latter gets nulled out immediately below.
1108 SetFocusedWindowInternal(window
, aActionId
);
1110 mFocusedWindow
= nullptr;
1111 window
->SetFocusedElement(nullptr);
1115 // if the docshell being hidden is being destroyed, then we want to move
1116 // focus somewhere else. Call ClearFocus on the toplevel window, which
1117 // will have the effect of clearing the focus and moving the focused window
1118 // to the toplevel window. But if the window isn't being destroyed, we are
1119 // likely just loading a new document in it, so we want to maintain the
1120 // focused window so that the new document gets properly focused.
1121 bool beingDestroyed
= !docShellBeingHidden
;
1122 if (docShellBeingHidden
) {
1123 docShellBeingHidden
->IsBeingDestroyed(&beingDestroyed
);
1125 if (beingDestroyed
) {
1126 // There is usually no need to do anything if a toplevel window is going
1127 // away, as we assume that WindowLowered will be called. However, this may
1128 // not happen if nsIAppStartup::eForceQuit is used to quit, and can cause
1129 // a leak. So if the active window is being destroyed, call WindowLowered
1132 if (XRE_IsParentProcess()) {
1133 if (mActiveWindow
== mFocusedWindow
|| mActiveWindow
== window
) {
1134 WindowLowered(mActiveWindow
, aActionId
);
1136 ClearFocus(mActiveWindow
);
1139 BrowsingContext
* active
= GetActiveBrowsingContext();
1141 nsPIDOMWindowOuter
* activeWindow
= active
->GetDOMWindow();
1143 if ((mFocusedWindow
&&
1144 mFocusedWindow
->GetBrowsingContext() == active
) ||
1145 (window
->GetBrowsingContext() == active
)) {
1146 WindowLowered(activeWindow
, aActionId
);
1148 ClearFocus(activeWindow
);
1150 } // else do nothing when an out-of-process iframe is torn down
1156 // if the window being hidden is an ancestor of the focused window, adjust
1157 // the focused window so that it points to the one being hidden. This
1158 // ensures that the focused window isn't in a chain of frames that doesn't
1160 if (window
!= mFocusedWindow
) {
1161 nsCOMPtr
<nsIDocShellTreeItem
> dsti
=
1162 mFocusedWindow
? mFocusedWindow
->GetDocShell() : nullptr;
1164 nsCOMPtr
<nsIDocShellTreeItem
> parentDsti
;
1165 dsti
->GetInProcessParent(getter_AddRefs(parentDsti
));
1167 if (nsCOMPtr
<nsPIDOMWindowOuter
> parentWindow
=
1168 parentDsti
->GetWindow()) {
1169 parentWindow
->SetFocusedElement(nullptr);
1174 SetFocusedWindowInternal(window
, aActionId
);
1178 void nsFocusManager::FireDelayedEvents(Document
* aDocument
) {
1179 MOZ_ASSERT(aDocument
);
1181 // fire any delayed focus and blur events in the same order that they were
1183 for (uint32_t i
= 0; i
< mDelayedBlurFocusEvents
.Length(); i
++) {
1184 if (mDelayedBlurFocusEvents
[i
].mDocument
== aDocument
) {
1185 if (!aDocument
->GetInnerWindow() ||
1186 !aDocument
->GetInnerWindow()->IsCurrentInnerWindow()) {
1187 // If the document was navigated away from or is defunct, don't bother
1188 // firing events on it. Note the symmetry between this condition and
1189 // the similar one in Document.cpp:FireOrClearDelayedEvents.
1190 mDelayedBlurFocusEvents
.RemoveElementAt(i
);
1192 } else if (!aDocument
->EventHandlingSuppressed()) {
1193 EventMessage message
= mDelayedBlurFocusEvents
[i
].mEventMessage
;
1194 nsCOMPtr
<EventTarget
> target
= mDelayedBlurFocusEvents
[i
].mTarget
;
1195 RefPtr
<PresShell
> presShell
= mDelayedBlurFocusEvents
[i
].mPresShell
;
1196 nsCOMPtr
<EventTarget
> relatedTarget
=
1197 mDelayedBlurFocusEvents
[i
].mRelatedTarget
;
1198 mDelayedBlurFocusEvents
.RemoveElementAt(i
);
1200 FireFocusOrBlurEvent(message
, presShell
, target
, false, false,
1208 void nsFocusManager::WasNuked(nsPIDOMWindowOuter
* aWindow
) {
1209 MOZ_ASSERT(aWindow
, "Expected non-null window.");
1210 MOZ_ASSERT(aWindow
!= mActiveWindow
,
1211 "How come we're nuking a window that's still active?");
1212 if (aWindow
== mFocusedWindow
) {
1213 mFocusedWindow
= nullptr;
1214 SetFocusedBrowsingContext(nullptr, GenerateFocusActionId());
1215 mFocusedElement
= nullptr;
1219 nsresult
nsFocusManager::FocusPlugin(Element
* aPlugin
) {
1220 NS_ENSURE_ARG(aPlugin
);
1221 SetFocusInner(aPlugin
, 0, true, false, GenerateFocusActionId());
1225 nsFocusManager::BlurredElementInfo::BlurredElementInfo(Element
& aElement
)
1226 : mElement(aElement
) {}
1228 nsFocusManager::BlurredElementInfo::~BlurredElementInfo() = default;
1230 // https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo
1231 static bool ShouldMatchFocusVisible(nsPIDOMWindowOuter
* aWindow
,
1232 const Element
& aElement
,
1233 int32_t aFocusFlags
) {
1234 // If we were explicitly requested to show the ring, do it.
1235 if (aFocusFlags
& nsIFocusManager::FLAG_SHOWRING
) {
1239 if (aWindow
->ShouldShowFocusRing()) {
1240 // The window decision also trumps any other heuristic.
1244 // Any element which supports keyboard input (such as an input element, or any
1245 // other element which may trigger a virtual keyboard to be shown on focus if
1246 // a physical keyboard is not present) should always match :focus-visible when
1249 if (aElement
.IsHTMLElement(nsGkAtoms::textarea
) || aElement
.IsEditable()) {
1253 if (auto* input
= HTMLInputElement::FromNode(aElement
)) {
1254 if (input
->IsSingleLineTextControl()) {
1260 if (aFocusFlags
& nsIFocusManager::FLAG_NOSHOWRING
) {
1264 switch (nsFocusManager::GetFocusMoveActionCause(aFocusFlags
)) {
1265 case InputContextAction::CAUSE_KEY
:
1266 // If the user interacts with the page via the keyboard, the currently
1267 // focused element should match :focus-visible (i.e. keyboard usage may
1268 // change whether this pseudo-class matches even if it doesn't affect
1271 case InputContextAction::CAUSE_UNKNOWN
:
1272 // We render outlines if the last "known" focus method was by key or there
1273 // was no previous known focus method, otherwise we don't.
1274 return aWindow
->UnknownFocusMethodShouldShowOutline();
1275 case InputContextAction::CAUSE_MOUSE
:
1276 case InputContextAction::CAUSE_TOUCH
:
1277 case InputContextAction::CAUSE_LONGPRESS
:
1278 // If the user interacts with the page via a pointing device, such that
1279 // the focus is moved to a new element which does not support user input,
1280 // the newly focused element should not match :focus-visible.
1282 case InputContextAction::CAUSE_UNKNOWN_CHROME
:
1283 case InputContextAction::CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT
:
1284 case InputContextAction::CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT
:
1285 // TODO(emilio): We could return some of these though, looking at
1286 // UserActivation. We may want to suppress focus rings for unknown /
1287 // programatic focus if the user is interacting with the page but not
1288 // during keyboard input, or such.
1289 MOZ_ASSERT_UNREACHABLE(
1290 "These don't get returned by GetFocusMoveActionCause");
1297 void nsFocusManager::NotifyFocusStateChange(Element
* aElement
,
1298 Element
* aElementToFocus
,
1299 int32_t aFlags
, bool aGettingFocus
,
1300 bool aShouldShowFocusRing
) {
1301 MOZ_ASSERT_IF(aElementToFocus
, !aGettingFocus
);
1302 nsIContent
* commonAncestor
= nullptr;
1303 if (aElementToFocus
) {
1304 commonAncestor
= nsContentUtils::GetCommonFlattenedTreeAncestor(
1305 aElement
, aElementToFocus
);
1308 if (aGettingFocus
) {
1309 EventStates eventStateToAdd
= NS_EVENT_STATE_FOCUS
;
1310 if (aShouldShowFocusRing
) {
1311 eventStateToAdd
|= NS_EVENT_STATE_FOCUSRING
;
1313 aElement
->AddStates(eventStateToAdd
);
1315 EventStates eventStateToRemove
=
1316 NS_EVENT_STATE_FOCUS
| NS_EVENT_STATE_FOCUSRING
;
1317 aElement
->RemoveStates(eventStateToRemove
);
1320 for (nsIContent
* content
= aElement
; content
&& content
!= commonAncestor
;
1321 content
= content
->GetFlattenedTreeParent()) {
1322 Element
* element
= Element::FromNode(content
);
1327 if (aGettingFocus
) {
1328 if (element
->State().HasState(NS_EVENT_STATE_FOCUS_WITHIN
)) {
1331 element
->AddStates(NS_EVENT_STATE_FOCUS_WITHIN
);
1333 element
->RemoveStates(NS_EVENT_STATE_FOCUS_WITHIN
);
1339 void nsFocusManager::EnsureCurrentWidgetFocused(CallerType aCallerType
) {
1340 if (!mFocusedWindow
|| sTestMode
) return;
1342 // get the main child widget for the focused window and ensure that the
1343 // platform knows that this widget is focused.
1344 nsCOMPtr
<nsIDocShell
> docShell
= mFocusedWindow
->GetDocShell();
1348 RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
1352 nsViewManager
* vm
= presShell
->GetViewManager();
1356 nsCOMPtr
<nsIWidget
> widget
;
1357 vm
->GetRootWidget(getter_AddRefs(widget
));
1361 widget
->SetFocus(nsIWidget::Raise::No
, aCallerType
);
1364 void nsFocusManager::ActivateOrDeactivate(nsPIDOMWindowOuter
* aWindow
,
1366 MOZ_ASSERT(XRE_IsParentProcess());
1371 if (BrowsingContext
* bc
= aWindow
->GetBrowsingContext()) {
1372 MOZ_ASSERT(bc
->IsTop());
1374 RefPtr
<CanonicalBrowsingContext
> chromeTop
=
1375 bc
->Canonical()->TopCrossChromeBoundary();
1376 MOZ_ASSERT(bc
== chromeTop
);
1378 chromeTop
->SetIsActiveBrowserWindow(aActive
);
1379 chromeTop
->CallOnAllTopDescendants(
1380 [aActive
](CanonicalBrowsingContext
* aBrowsingContext
) -> CallState
{
1381 aBrowsingContext
->SetIsActiveBrowserWindow(aActive
);
1382 return CallState::Continue
;
1386 if (aWindow
->GetExtantDoc()) {
1387 nsContentUtils::DispatchEventOnlyToChrome(
1388 aWindow
->GetExtantDoc(), aWindow
->GetCurrentInnerWindow(),
1389 aActive
? u
"activate"_ns
: u
"deactivate"_ns
, CanBubble::eYes
,
1390 Cancelable::eYes
, nullptr);
1394 // Retrieves innerWindowId of the window of the last focused element to
1395 // log a warning to the website console.
1396 void LogWarningFullscreenWindowRaise(Element
* aElement
) {
1397 nsCOMPtr
<nsFrameLoaderOwner
> frameLoaderOwner(do_QueryInterface(aElement
));
1398 NS_ENSURE_TRUE_VOID(frameLoaderOwner
);
1400 RefPtr
<nsFrameLoader
> frameLoader
= frameLoaderOwner
->GetFrameLoader();
1401 NS_ENSURE_TRUE_VOID(frameLoaderOwner
);
1403 RefPtr
<BrowsingContext
> browsingContext
= frameLoader
->GetBrowsingContext();
1404 NS_ENSURE_TRUE_VOID(browsingContext
);
1406 WindowGlobalParent
* windowGlobalParent
=
1407 browsingContext
->Canonical()->GetCurrentWindowGlobal();
1408 NS_ENSURE_TRUE_VOID(windowGlobalParent
);
1411 nsAutoString localizedMsg
;
1412 nsTArray
<nsString
> params
;
1413 nsresult rv
= nsContentUtils::FormatLocalizedString(
1414 nsContentUtils::eDOM_PROPERTIES
, "FullscreenExitWindowFocus", params
,
1417 NS_ENSURE_SUCCESS_VOID(rv
);
1419 Unused
<< nsContentUtils::ReportToConsoleByWindowID(
1420 localizedMsg
, nsIScriptError::warningFlag
, "DOM"_ns
,
1421 windowGlobalParent
->InnerWindowId(),
1422 windowGlobalParent
->GetDocumentURI());
1425 void nsFocusManager::SetFocusInner(Element
* aNewContent
, int32_t aFlags
,
1426 bool aFocusChanged
, bool aAdjustWidget
,
1427 uint64_t aActionId
) {
1428 // if the element is not focusable, just return and leave the focus as is
1429 RefPtr
<Element
> elementToFocus
=
1430 FlushAndCheckIfFocusable(aNewContent
, aFlags
);
1431 if (!elementToFocus
) {
1435 RefPtr
<BrowsingContext
> focusedBrowsingContext
= GetFocusedBrowsingContext();
1437 // check if the element to focus is a frame (iframe) containing a child
1438 // document. Frames are never directly focused; instead focusing a frame
1439 // means focus what is inside the frame. To do this, the descendant content
1440 // within the frame is retrieved and that will be focused instead.
1441 nsCOMPtr
<nsPIDOMWindowOuter
> newWindow
;
1442 nsCOMPtr
<nsPIDOMWindowOuter
> subWindow
= GetContentWindow(elementToFocus
);
1444 elementToFocus
= GetFocusedDescendant(subWindow
, eIncludeAllDescendants
,
1445 getter_AddRefs(newWindow
));
1447 // since a window is being refocused, clear aFocusChanged so that the
1448 // caret position isn't updated.
1449 aFocusChanged
= false;
1452 // unless it was set above, retrieve the window for the element to focus
1454 newWindow
= GetCurrentWindow(elementToFocus
);
1457 BrowsingContext
* newBrowsingContext
= nullptr;
1459 newBrowsingContext
= newWindow
->GetBrowsingContext();
1462 // if the element is already focused, just return. Note that this happens
1463 // after the frame check above so that we compare the element that will be
1464 // focused rather than the frame it is in.
1465 if (!newWindow
|| (newBrowsingContext
== GetFocusedBrowsingContext() &&
1466 elementToFocus
== mFocusedElement
)) {
1470 MOZ_ASSERT(newBrowsingContext
);
1472 BrowsingContext
* browsingContextToFocus
= newBrowsingContext
;
1473 if (RefPtr
<nsFrameLoaderOwner
> flo
= do_QueryObject(elementToFocus
)) {
1474 // Only look at pre-existing browsing contexts. If this function is
1475 // called during reflow, calling GetBrowsingContext() could cause frame
1476 // loader initialization at a time when it isn't safe.
1477 if (BrowsingContext
* bc
= flo
->GetExtantBrowsingContext()) {
1478 browsingContextToFocus
= bc
;
1482 // don't allow focus to be placed in docshells or descendants of docshells
1483 // that are being destroyed. Also, ensure that the page hasn't been
1484 // unloaded. The prevents content from being refocused during an unload event.
1485 nsCOMPtr
<nsIDocShell
> newDocShell
= newWindow
->GetDocShell();
1486 nsCOMPtr
<nsIDocShell
> docShell
= newDocShell
;
1489 docShell
->GetIsInUnload(&inUnload
);
1494 bool beingDestroyed
;
1495 docShell
->IsBeingDestroyed(&beingDestroyed
);
1496 if (beingDestroyed
) {
1500 BrowsingContext
* bc
= docShell
->GetBrowsingContext();
1502 nsCOMPtr
<nsIDocShellTreeItem
> parentDsti
;
1503 docShell
->GetInProcessParent(getter_AddRefs(parentDsti
));
1504 docShell
= do_QueryInterface(parentDsti
);
1505 if (!docShell
&& !XRE_IsParentProcess()) {
1506 // We don't have an in-process parent, but let's see if we have
1507 // an in-process ancestor or if an out-of-process ancestor
1510 bc
= bc
->GetParent();
1511 if (bc
&& bc
->IsDiscarded()) {
1514 } while (bc
&& !bc
->IsInProcess());
1516 docShell
= bc
->GetDocShell();
1523 bool focusMovesToDifferentBC
=
1524 (focusedBrowsingContext
!= browsingContextToFocus
);
1526 if (focusedBrowsingContext
&& focusMovesToDifferentBC
&&
1527 nsContentUtils::IsHandlingKeyBoardEvent() &&
1528 !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
1529 MOZ_ASSERT(browsingContextToFocus
,
1530 "BrowsingContext to focus should be non-null.");
1532 nsIPrincipal
* focusedPrincipal
= nullptr;
1533 nsIPrincipal
* newPrincipal
= nullptr;
1535 if (XRE_IsParentProcess()) {
1536 if (WindowGlobalParent
* focusedWindowGlobalParent
=
1537 focusedBrowsingContext
->Canonical()->GetCurrentWindowGlobal()) {
1538 focusedPrincipal
= focusedWindowGlobalParent
->DocumentPrincipal();
1541 if (WindowGlobalParent
* newWindowGlobalParent
=
1542 browsingContextToFocus
->Canonical()->GetCurrentWindowGlobal()) {
1543 newPrincipal
= newWindowGlobalParent
->DocumentPrincipal();
1545 } else if (focusedBrowsingContext
->IsInProcess() &&
1546 browsingContextToFocus
->IsInProcess()) {
1547 nsCOMPtr
<nsIScriptObjectPrincipal
> focused
=
1548 do_QueryInterface(focusedBrowsingContext
->GetDOMWindow());
1549 nsCOMPtr
<nsIScriptObjectPrincipal
> newFocus
=
1550 do_QueryInterface(browsingContextToFocus
->GetDOMWindow());
1551 MOZ_ASSERT(focused
&& newFocus
,
1552 "BrowsingContext should always have a window here.");
1553 focusedPrincipal
= focused
->GetPrincipal();
1554 newPrincipal
= newFocus
->GetPrincipal();
1557 if (!focusedPrincipal
|| !newPrincipal
) {
1561 if (!focusedPrincipal
->Subsumes(newPrincipal
)) {
1562 NS_WARNING("Not allowed to focus the new window!");
1567 // to check if the new element is in the active window, compare the
1568 // new root docshell for the new element with the active window's docshell.
1569 RefPtr
<BrowsingContext
> newRootBrowsingContext
= nullptr;
1570 bool isElementInActiveWindow
= false;
1571 if (XRE_IsParentProcess()) {
1572 nsCOMPtr
<nsPIDOMWindowOuter
> newRootWindow
= nullptr;
1573 nsCOMPtr
<nsIDocShellTreeItem
> dsti
= newWindow
->GetDocShell();
1575 nsCOMPtr
<nsIDocShellTreeItem
> root
;
1576 dsti
->GetInProcessRootTreeItem(getter_AddRefs(root
));
1577 newRootWindow
= root
? root
->GetWindow() : nullptr;
1579 isElementInActiveWindow
=
1580 (mActiveWindow
&& newRootWindow
== mActiveWindow
);
1582 if (newRootWindow
) {
1583 newRootBrowsingContext
= newRootWindow
->GetBrowsingContext();
1586 // XXX This is wrong for `<iframe mozbrowser>` and for XUL
1587 // `<browser remote="true">`. See:
1588 // https://searchfox.org/mozilla-central/rev/8a63fc190b39ed6951abb4aef4a56487a43962bc/dom/base/nsFrameLoader.cpp#229-232
1589 newRootBrowsingContext
= newBrowsingContext
->Top();
1590 // to check if the new element is in the active window, compare the
1591 // new root docshell for the new element with the active window's docshell.
1592 isElementInActiveWindow
=
1593 (GetActiveBrowsingContext() == newRootBrowsingContext
);
1596 // Exit fullscreen if a website focuses another window
1597 if (StaticPrefs::full_screen_api_exit_on_windowRaise() &&
1598 !isElementInActiveWindow
&&
1599 aFlags
& (FLAG_RAISE
| FLAG_NONSYSTEMCALLER
)) {
1600 if (XRE_IsParentProcess()) {
1601 if (Document
* doc
= mActiveWindow
? mActiveWindow
->GetDoc() : nullptr) {
1602 if (doc
->GetFullscreenElement()) {
1603 LogWarningFullscreenWindowRaise(mFocusedElement
);
1604 Document::AsyncExitFullscreen(doc
);
1608 BrowsingContext
* activeBrowsingContext
= GetActiveBrowsingContext();
1609 if (activeBrowsingContext
) {
1610 nsIDocShell
* shell
= activeBrowsingContext
->GetDocShell();
1612 Document
* doc
= shell
->GetDocument();
1613 if (doc
&& doc
->GetFullscreenElement()) {
1614 Document::AsyncExitFullscreen(doc
);
1617 mozilla::dom::ContentChild
* contentChild
=
1618 mozilla::dom::ContentChild::GetSingleton();
1619 MOZ_ASSERT(contentChild
);
1620 contentChild
->SendMaybeExitFullscreen(activeBrowsingContext
);
1626 // Exit fullscreen if we're focusing a windowed plugin on a non-MacOSX
1627 // system. We don't control event dispatch to windowed plugins on non-MacOSX,
1628 // so we can't display the "Press ESC to leave fullscreen mode" warning on
1629 // key input if a windowed plugin is focused, so just exit fullscreen
1630 // to guard against phishing.
1632 if (elementToFocus
&&
1633 nsContentUtils::GetRootDocument(elementToFocus
->OwnerDoc())
1634 ->GetFullscreenElement() &&
1635 nsContentUtils::HasPluginWithUncontrolledEventDispatch(elementToFocus
)) {
1636 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag
, "DOM"_ns
,
1637 elementToFocus
->OwnerDoc(),
1638 nsContentUtils::eDOM_PROPERTIES
,
1639 "FocusedWindowedPluginWhileFullscreen");
1640 Document::AsyncExitFullscreen(elementToFocus
->OwnerDoc());
1644 // if the FLAG_NOSWITCHFRAME flag is used, only allow the focus to be
1645 // shifted away from the current element if the new shell to focus is
1646 // the same or an ancestor shell of the currently focused shell.
1647 bool allowFrameSwitch
= !(aFlags
& FLAG_NOSWITCHFRAME
) ||
1648 IsSameOrAncestor(newWindow
, focusedBrowsingContext
);
1650 // if the element is in the active window, frame switching is allowed and
1651 // the content is in a visible window, fire blur and focus events.
1652 bool sendFocusEvent
=
1653 isElementInActiveWindow
&& allowFrameSwitch
&& IsWindowVisible(newWindow
);
1655 // Don't allow to steal the focus from chrome nodes if the caller cannot
1657 if (sendFocusEvent
&& mFocusedElement
&&
1658 mFocusedElement
->OwnerDoc() != aNewContent
->OwnerDoc() &&
1659 mFocusedElement
->NodePrincipal()->IsSystemPrincipal() &&
1660 !nsContentUtils::LegacyIsCallerNativeCode() &&
1661 !nsContentUtils::CanCallerAccess(mFocusedElement
)) {
1662 sendFocusEvent
= false;
1665 LOGCONTENT("Shift Focus: %s", elementToFocus
.get());
1666 LOGFOCUS((" Flags: %x Current Window: %p New Window: %p Current Element: %p",
1667 aFlags
, mFocusedWindow
.get(), newWindow
.get(),
1668 mFocusedElement
.get()));
1670 (" In Active Window: %d Moves to different BrowsingContext: %d "
1672 isElementInActiveWindow
, focusMovesToDifferentBC
, sendFocusEvent
));
1674 if (sendFocusEvent
) {
1675 Maybe
<BlurredElementInfo
> blurredInfo
;
1676 if (mFocusedElement
) {
1677 blurredInfo
.emplace(*mFocusedElement
);
1679 // return if blurring fails or the focus changes during the blur
1680 if (focusedBrowsingContext
) {
1681 // find the common ancestor of the currently focused window and the new
1682 // window. The ancestor will need to have its currently focused node
1683 // cleared once the document has been blurred. Otherwise, we'll be in a
1684 // state where a document is blurred yet the chain of windows above it
1685 // still points to that document.
1686 // For instance, in the following frame tree:
1690 // D is focused and we want to focus C. Once D has been blurred, we need
1691 // to clear out the focus in A, otherwise A would still maintain that B
1692 // was focused, and B that D was focused.
1693 RefPtr
<BrowsingContext
> commonAncestor
;
1694 if (focusMovesToDifferentBC
) {
1695 commonAncestor
= GetCommonAncestor(newWindow
, focusedBrowsingContext
);
1698 bool needToClearFocusedElement
= false;
1699 if (focusedBrowsingContext
->IsChrome()) {
1700 // Always reset focused element if focus is currently in chrome window.
1701 needToClearFocusedElement
= true;
1703 // Only reset focused element if focus moves within the same top-level
1705 if (focusedBrowsingContext
->Top() == browsingContextToFocus
->Top()) {
1706 // XXX for the case that we try to focus an
1707 // already-focused-remote-frame, we would still send blur and focus
1708 // IPC to it, but they will not generate blur or focus event, we don't
1709 // want to reset activeElement on the remote frame.
1710 needToClearFocusedElement
= (focusMovesToDifferentBC
||
1711 focusedBrowsingContext
->IsInProcess());
1715 if (!Blur(needToClearFocusedElement
? focusedBrowsingContext
.get()
1717 commonAncestor
? commonAncestor
.get() : nullptr,
1718 focusMovesToDifferentBC
, aAdjustWidget
, aActionId
,
1724 Focus(newWindow
, elementToFocus
, aFlags
, focusMovesToDifferentBC
,
1725 aFocusChanged
, false, aAdjustWidget
, aActionId
, blurredInfo
);
1727 // otherwise, for inactive windows and when the caller cannot steal the
1728 // focus, update the node in the window, and raise the window if desired.
1729 if (allowFrameSwitch
) {
1730 AdjustWindowFocus(newBrowsingContext
, true, IsWindowVisible(newWindow
),
1734 // set the focus node and method as needed
1735 uint32_t focusMethod
=
1736 aFocusChanged
? aFlags
& FOCUSMETHODANDRING_MASK
1737 : newWindow
->GetFocusMethod() |
1738 (aFlags
& (FLAG_SHOWRING
| FLAG_NOSHOWRING
));
1739 newWindow
->SetFocusedElement(elementToFocus
, focusMethod
);
1740 if (aFocusChanged
) {
1741 nsCOMPtr
<nsIDocShell
> docShell
= newWindow
->GetDocShell();
1743 RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
1744 if (presShell
&& presShell
->DidInitialize()) {
1745 ScrollIntoView(presShell
, elementToFocus
, aFlags
);
1749 // update the commands even when inactive so that the attributes for that
1750 // window are up to date.
1751 if (allowFrameSwitch
) {
1752 newWindow
->UpdateCommands(u
"focus"_ns
, nullptr, 0);
1755 if (aFlags
& FLAG_RAISE
) {
1756 if (newRootBrowsingContext
) {
1757 if (XRE_IsParentProcess() || newRootBrowsingContext
->IsInProcess()) {
1758 RaiseWindow(newRootBrowsingContext
->GetDOMWindow(),
1759 aFlags
& FLAG_NONSYSTEMCALLER
? CallerType::NonSystem
1760 : CallerType::System
,
1763 mozilla::dom::ContentChild
* contentChild
=
1764 mozilla::dom::ContentChild::GetSingleton();
1765 MOZ_ASSERT(contentChild
);
1766 contentChild
->SendRaiseWindow(newRootBrowsingContext
,
1767 aFlags
& FLAG_NONSYSTEMCALLER
1768 ? CallerType::NonSystem
1769 : CallerType::System
,
1777 static already_AddRefed
<BrowsingContext
> GetParentIgnoreChromeBoundary(
1778 BrowsingContext
* aBC
) {
1779 // Chrome BrowsingContexts are only available in the parent process, so if
1780 // we're in a content process, we only worry about the context tree.
1781 if (XRE_IsParentProcess()) {
1782 return aBC
->Canonical()->GetParentCrossChromeBoundary();
1784 return do_AddRef(aBC
->GetParent());
1787 bool nsFocusManager::IsSameOrAncestor(BrowsingContext
* aPossibleAncestor
,
1788 BrowsingContext
* aContext
) const {
1789 if (!aPossibleAncestor
) {
1793 for (RefPtr
<BrowsingContext
> bc
= aContext
; bc
;
1794 bc
= GetParentIgnoreChromeBoundary(bc
)) {
1795 if (bc
== aPossibleAncestor
) {
1803 bool nsFocusManager::IsSameOrAncestor(nsPIDOMWindowOuter
* aPossibleAncestor
,
1804 nsPIDOMWindowOuter
* aWindow
) const {
1805 if (aWindow
&& aPossibleAncestor
) {
1806 return IsSameOrAncestor(aPossibleAncestor
->GetBrowsingContext(),
1807 aWindow
->GetBrowsingContext());
1812 bool nsFocusManager::IsSameOrAncestor(nsPIDOMWindowOuter
* aPossibleAncestor
,
1813 BrowsingContext
* aContext
) const {
1814 if (aPossibleAncestor
) {
1815 return IsSameOrAncestor(aPossibleAncestor
->GetBrowsingContext(), aContext
);
1820 bool nsFocusManager::IsSameOrAncestor(BrowsingContext
* aPossibleAncestor
,
1821 nsPIDOMWindowOuter
* aWindow
) const {
1823 return IsSameOrAncestor(aPossibleAncestor
, aWindow
->GetBrowsingContext());
1828 mozilla::dom::BrowsingContext
* nsFocusManager::GetCommonAncestor(
1829 nsPIDOMWindowOuter
* aWindow
, mozilla::dom::BrowsingContext
* aContext
) {
1830 NS_ENSURE_TRUE(aWindow
&& aContext
, nullptr);
1832 if (XRE_IsParentProcess()) {
1833 nsCOMPtr
<nsIDocShellTreeItem
> dsti1
= aWindow
->GetDocShell();
1834 NS_ENSURE_TRUE(dsti1
, nullptr);
1836 nsCOMPtr
<nsIDocShellTreeItem
> dsti2
= aContext
->GetDocShell();
1837 NS_ENSURE_TRUE(dsti2
, nullptr);
1839 AutoTArray
<nsIDocShellTreeItem
*, 30> parents1
, parents2
;
1841 parents1
.AppendElement(dsti1
);
1842 nsCOMPtr
<nsIDocShellTreeItem
> parentDsti1
;
1843 dsti1
->GetInProcessParent(getter_AddRefs(parentDsti1
));
1844 dsti1
.swap(parentDsti1
);
1847 parents2
.AppendElement(dsti2
);
1848 nsCOMPtr
<nsIDocShellTreeItem
> parentDsti2
;
1849 dsti2
->GetInProcessParent(getter_AddRefs(parentDsti2
));
1850 dsti2
.swap(parentDsti2
);
1853 uint32_t pos1
= parents1
.Length();
1854 uint32_t pos2
= parents2
.Length();
1855 nsIDocShellTreeItem
* parent
= nullptr;
1857 for (len
= std::min(pos1
, pos2
); len
> 0; --len
) {
1858 nsIDocShellTreeItem
* child1
= parents1
.ElementAt(--pos1
);
1859 nsIDocShellTreeItem
* child2
= parents2
.ElementAt(--pos2
);
1860 if (child1
!= child2
) {
1866 return parent
? parent
->GetBrowsingContext() : nullptr;
1869 BrowsingContext
* bc1
= aWindow
->GetBrowsingContext();
1870 NS_ENSURE_TRUE(bc1
, nullptr);
1872 BrowsingContext
* bc2
= aContext
;
1874 AutoTArray
<BrowsingContext
*, 30> parents1
, parents2
;
1876 parents1
.AppendElement(bc1
);
1877 bc1
= bc1
->GetParent();
1880 parents2
.AppendElement(bc2
);
1881 bc2
= bc2
->GetParent();
1884 uint32_t pos1
= parents1
.Length();
1885 uint32_t pos2
= parents2
.Length();
1886 BrowsingContext
* parent
= nullptr;
1888 for (len
= std::min(pos1
, pos2
); len
> 0; --len
) {
1889 BrowsingContext
* child1
= parents1
.ElementAt(--pos1
);
1890 BrowsingContext
* child2
= parents2
.ElementAt(--pos2
);
1891 if (child1
!= child2
) {
1900 bool nsFocusManager::AdjustInProcessWindowFocus(
1901 BrowsingContext
* aBrowsingContext
, bool aCheckPermission
, bool aIsVisible
,
1902 uint64_t aActionId
) {
1903 BrowsingContext
* bc
= aBrowsingContext
;
1904 bool needToNotifyOtherProcess
= false;
1906 // get the containing <iframe> or equivalent element so that it can be
1908 nsCOMPtr
<Element
> frameElement
= bc
->GetEmbedderElement();
1909 BrowsingContext
* parent
= bc
->GetParent();
1910 if (!parent
&& XRE_IsParentProcess()) {
1911 CanonicalBrowsingContext
* canonical
= bc
->Canonical();
1912 RefPtr
<WindowGlobalParent
> embedder
=
1913 canonical
->GetEmbedderWindowGlobal();
1915 parent
= embedder
->BrowsingContext();
1922 if (!frameElement
&& XRE_IsContentProcess()) {
1923 needToNotifyOtherProcess
= true;
1927 nsCOMPtr
<nsPIDOMWindowOuter
> window
= bc
->GetDOMWindow();
1929 // if the parent window is visible but the original window was not, then we
1930 // have likely moved up and out from a hidden tab to the browser window, or
1931 // a similar such arrangement. Stop adjusting the current nodes.
1932 if (IsWindowVisible(window
) != aIsVisible
) {
1936 // When aCheckPermission is true, we should check whether the caller can
1937 // access the window or not. If it cannot access, we should stop the
1939 if (aCheckPermission
&& !nsContentUtils::LegacyIsCallerNativeCode() &&
1940 !nsContentUtils::CanCallerAccess(window
->GetCurrentInnerWindow())) {
1944 if (frameElement
!= window
->GetFocusedElement()) {
1945 window
->SetFocusedElement(frameElement
);
1947 RefPtr
<nsFrameLoaderOwner
> loaderOwner
= do_QueryObject(frameElement
);
1948 MOZ_ASSERT(loaderOwner
);
1949 RefPtr
<nsFrameLoader
> loader
= loaderOwner
->GetFrameLoader();
1950 if (loader
&& loader
->IsRemoteFrame() &&
1951 GetFocusedBrowsingContext() == bc
) {
1952 Blur(nullptr, nullptr, true, true, aActionId
);
1956 return needToNotifyOtherProcess
;
1959 void nsFocusManager::AdjustWindowFocus(BrowsingContext
* aBrowsingContext
,
1960 bool aCheckPermission
, bool aIsVisible
,
1961 uint64_t aActionId
) {
1962 if (AdjustInProcessWindowFocus(aBrowsingContext
, aCheckPermission
, aIsVisible
,
1964 // Some ancestors of aBrowsingContext isn't in this process, so notify other
1965 // processes to adjust their focused element.
1966 mozilla::dom::ContentChild
* contentChild
=
1967 mozilla::dom::ContentChild::GetSingleton();
1968 MOZ_ASSERT(contentChild
);
1969 contentChild
->SendAdjustWindowFocus(aBrowsingContext
, aIsVisible
,
1974 bool nsFocusManager::IsWindowVisible(nsPIDOMWindowOuter
* aWindow
) {
1975 if (!aWindow
|| aWindow
->IsFrozen()) {
1979 // Check if the inner window is frozen as well. This can happen when a focus
1980 // change occurs while restoring a previous page.
1981 nsPIDOMWindowInner
* innerWindow
= aWindow
->GetCurrentInnerWindow();
1982 if (!innerWindow
|| innerWindow
->IsFrozen()) {
1986 nsCOMPtr
<nsIDocShell
> docShell
= aWindow
->GetDocShell();
1987 nsCOMPtr
<nsIBaseWindow
> baseWin(do_QueryInterface(docShell
));
1992 bool visible
= false;
1993 baseWin
->GetVisibility(&visible
);
1997 bool nsFocusManager::IsNonFocusableRoot(nsIContent
* aContent
) {
1998 MOZ_ASSERT(aContent
, "aContent must not be NULL");
1999 MOZ_ASSERT(aContent
->IsInComposedDoc(), "aContent must be in a document");
2001 // If aContent is in designMode, the root element is not focusable.
2002 // NOTE: in designMode, most elements are not focusable, just the document is
2004 // Also, if aContent is not editable but it isn't in designMode, it's not
2006 // And in userfocusignored context nothing is focusable.
2007 Document
* doc
= aContent
->GetComposedDoc();
2008 NS_ASSERTION(doc
, "aContent must have current document");
2009 return aContent
== doc
->GetRootElement() &&
2010 (doc
->HasFlag(NODE_IS_EDITABLE
) || !aContent
->IsEditable());
2013 Element
* nsFocusManager::FlushAndCheckIfFocusable(Element
* aElement
,
2019 nsCOMPtr
<Document
> doc
= aElement
->GetComposedDoc();
2020 // can't focus elements that are not in documents
2022 LOGCONTENT("Cannot focus %s because content not in document", aElement
)
2026 // Make sure that our frames are up to date while ensuring the presshell is
2027 // also initialized in case we come from a script calling focus() early.
2028 mEventHandlingNeedsFlush
= false;
2029 doc
->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames
);
2031 // this is a special case for some XUL elements or input number, where an
2032 // anonymous child is actually focusable and not the element itself.
2033 if (RefPtr
<Element
> redirectedFocus
= GetRedirectedFocus(aElement
)) {
2034 return FlushAndCheckIfFocusable(redirectedFocus
, aFlags
);
2037 PresShell
* presShell
= doc
->GetPresShell();
2042 // the root content can always be focused,
2043 // except in userfocusignored context.
2044 if (aElement
== doc
->GetRootElement()) {
2048 nsIFrame
* frame
= aElement
->GetPrimaryFrame();
2050 LOGCONTENT("Cannot focus %s as it has no frame", aElement
)
2054 if (aElement
->IsHTMLElement(nsGkAtoms::area
)) {
2055 // HTML areas do not have their own frame, and the img frame we get from
2056 // GetPrimaryFrame() is not relevant as to whether it is focusable or
2057 // not, so we have to do all the relevant checks manually for them.
2058 return frame
->IsVisibleConsideringAncestors() && aElement
->IsFocusable()
2063 // If this is an iframe that doesn't have an in-process subdocument, it is
2064 // either an OOP iframe or an in-process iframe without lazy about:blank
2065 // creation having taken place. In the OOP case, treat the frame as
2066 // focusable for consistency with Chrome. In the in-process case, create
2067 // the initial about:blank for in-process BrowsingContexts in order to
2068 // have the `GetSubDocumentFor` call after this block return something.
2069 if (RefPtr
<nsFrameLoaderOwner
> flo
= do_QueryObject(aElement
)) {
2070 // dom/webauthn/tests/browser/browser_abort_visibility.js fails without
2071 // the exclusion of XUL.
2072 if (aElement
->NodeInfo()->NamespaceID() != kNameSpaceID_XUL
) {
2073 // Only look at pre-existing browsing contexts. If this function is
2074 // called during reflow, calling GetBrowsingContext() could cause frame
2075 // loader initialization at a time when it isn't safe.
2076 if (BrowsingContext
* bc
= flo
->GetExtantBrowsingContext()) {
2077 // This call may create a contentViewer-created about:blank.
2078 // That's intentional, so we can move focus there.
2079 Document
* subdoc
= bc
->GetDocument();
2083 nsIPrincipal
* framerPrincipal
= doc
->GetPrincipal();
2084 nsIPrincipal
* frameePrincipal
= subdoc
->GetPrincipal();
2085 if (framerPrincipal
&& frameePrincipal
&&
2086 !framerPrincipal
->Equals(frameePrincipal
)) {
2087 // Assume focusability of different-origin iframes even in the
2088 // in-process case for consistency with the OOP case.
2089 // This is likely already the case anyway, but in case not,
2090 // this makes it explicitly so.
2097 return frame
->IsFocusable(aFlags
& FLAG_BYMOUSE
) ? aElement
: nullptr;
2100 bool nsFocusManager::Blur(BrowsingContext
* aBrowsingContextToClear
,
2101 BrowsingContext
* aAncestorBrowsingContextToFocus
,
2102 bool aIsLeavingDocument
, bool aAdjustWidget
,
2103 uint64_t aActionId
, Element
* aElementToFocus
) {
2104 if (XRE_IsParentProcess()) {
2105 return BlurImpl(aBrowsingContextToClear
, aAncestorBrowsingContextToFocus
,
2106 aIsLeavingDocument
, aAdjustWidget
, aElementToFocus
,
2109 mozilla::dom::ContentChild
* contentChild
=
2110 mozilla::dom::ContentChild::GetSingleton();
2111 MOZ_ASSERT(contentChild
);
2112 bool windowToClearHandled
= false;
2113 bool ancestorWindowToFocusHandled
= false;
2115 RefPtr
<BrowsingContext
> focusedBrowsingContext
= GetFocusedBrowsingContext();
2116 if (focusedBrowsingContext
&& focusedBrowsingContext
->IsDiscarded()) {
2117 focusedBrowsingContext
= nullptr;
2119 if (!focusedBrowsingContext
) {
2120 mFocusedElement
= nullptr;
2123 if (aBrowsingContextToClear
&& aBrowsingContextToClear
->IsDiscarded()) {
2124 aBrowsingContextToClear
= nullptr;
2126 if (aAncestorBrowsingContextToFocus
&&
2127 aAncestorBrowsingContextToFocus
->IsDiscarded()) {
2128 aAncestorBrowsingContextToFocus
= nullptr;
2130 // XXX should more early returns from BlurImpl be hoisted here to avoid
2131 // processing aBrowsingContextToClear and aAncestorBrowsingContextToFocus in
2132 // other processes when BlurImpl returns early in this process? Or should the
2133 // IPC messages for those be sent by BlurImpl itself, in which case they could
2135 if (focusedBrowsingContext
->IsInProcess()) {
2136 if (aBrowsingContextToClear
&& !aBrowsingContextToClear
->IsInProcess()) {
2137 MOZ_RELEASE_ASSERT(!(aAncestorBrowsingContextToFocus
&&
2138 !aAncestorBrowsingContextToFocus
->IsInProcess()),
2139 "Both aBrowsingContextToClear and "
2140 "aAncestorBrowsingContextToFocus are "
2142 contentChild
->SendSetFocusedElement(aBrowsingContextToClear
, false);
2144 if (aAncestorBrowsingContextToFocus
&&
2145 !aAncestorBrowsingContextToFocus
->IsInProcess()) {
2146 contentChild
->SendSetFocusedElement(aAncestorBrowsingContextToFocus
,
2149 return BlurImpl(aBrowsingContextToClear
, aAncestorBrowsingContextToFocus
,
2150 aIsLeavingDocument
, aAdjustWidget
, aElementToFocus
,
2153 if (aBrowsingContextToClear
&& aBrowsingContextToClear
->IsInProcess()) {
2154 nsPIDOMWindowOuter
* windowToClear
= aBrowsingContextToClear
->GetDOMWindow();
2155 MOZ_ASSERT(windowToClear
);
2156 windowToClear
->SetFocusedElement(nullptr);
2157 windowToClearHandled
= true;
2159 if (aAncestorBrowsingContextToFocus
&&
2160 aAncestorBrowsingContextToFocus
->IsInProcess()) {
2161 nsPIDOMWindowOuter
* ancestorWindowToFocus
=
2162 aAncestorBrowsingContextToFocus
->GetDOMWindow();
2163 MOZ_ASSERT(ancestorWindowToFocus
);
2164 ancestorWindowToFocus
->SetFocusedElement(nullptr, 0, true);
2165 ancestorWindowToFocusHandled
= true;
2167 // The expectation is that the blurring would eventually result in an IPC
2168 // message doing this anyway, but this doesn't happen if the focus is in OOP
2169 // iframe which won't try to bounce an IPC message to its parent frame.
2170 SetFocusedWindowInternal(nullptr, aActionId
);
2171 contentChild
->SendBlurToParent(
2172 focusedBrowsingContext
, aBrowsingContextToClear
,
2173 aAncestorBrowsingContextToFocus
, aIsLeavingDocument
, aAdjustWidget
,
2174 windowToClearHandled
, ancestorWindowToFocusHandled
, aActionId
);
2178 void nsFocusManager::BlurFromOtherProcess(
2179 mozilla::dom::BrowsingContext
* aFocusedBrowsingContext
,
2180 mozilla::dom::BrowsingContext
* aBrowsingContextToClear
,
2181 mozilla::dom::BrowsingContext
* aAncestorBrowsingContextToFocus
,
2182 bool aIsLeavingDocument
, bool aAdjustWidget
, uint64_t aActionId
) {
2183 if (aFocusedBrowsingContext
!= GetFocusedBrowsingContext()) {
2186 BlurImpl(aBrowsingContextToClear
, aAncestorBrowsingContextToFocus
,
2187 aIsLeavingDocument
, aAdjustWidget
, nullptr, aActionId
);
2190 bool nsFocusManager::BlurImpl(BrowsingContext
* aBrowsingContextToClear
,
2191 BrowsingContext
* aAncestorBrowsingContextToFocus
,
2192 bool aIsLeavingDocument
, bool aAdjustWidget
,
2193 Element
* aElementToFocus
, uint64_t aActionId
) {
2194 LOGFOCUS(("<<Blur begin>>"));
2196 // hold a reference to the focused content, which may be null
2197 RefPtr
<Element
> element
= mFocusedElement
;
2199 if (!element
->IsInComposedDoc()) {
2200 mFocusedElement
= nullptr;
2203 if (element
== mFirstBlurEvent
) {
2208 RefPtr
<BrowsingContext
> focusedBrowsingContext
= GetFocusedBrowsingContext();
2209 // hold a reference to the focused window
2210 nsCOMPtr
<nsPIDOMWindowOuter
> window
;
2211 if (focusedBrowsingContext
) {
2212 window
= focusedBrowsingContext
->GetDOMWindow();
2215 mFocusedElement
= nullptr;
2219 nsCOMPtr
<nsIDocShell
> docShell
= window
->GetDocShell();
2221 if (XRE_IsContentProcess() &&
2222 ActionIdComparableAndLower(
2223 aActionId
, mActionIdForFocusedBrowsingContextInContent
)) {
2224 // Unclear if this ever happens.
2226 ("Ignored an attempt to null out focused BrowsingContext when "
2227 "docShell is null due to a stale action id."));
2231 mFocusedWindow
= nullptr;
2232 // Setting focused BrowsingContext to nullptr to avoid leaking in print
2234 SetFocusedBrowsingContext(nullptr, aActionId
);
2235 mFocusedElement
= nullptr;
2239 // Keep a ref to presShell since dispatching the DOM event may cause
2240 // the document to be destroyed.
2241 RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
2243 if (XRE_IsContentProcess() &&
2244 ActionIdComparableAndLower(
2245 aActionId
, mActionIdForFocusedBrowsingContextInContent
)) {
2246 // Unclear if this ever happens.
2248 ("Ignored an attempt to null out focused BrowsingContext when "
2249 "presShell is null due to a stale action id."));
2252 mFocusedElement
= nullptr;
2253 mFocusedWindow
= nullptr;
2254 // Setting focused BrowsingContext to nullptr to avoid leaking in print
2256 SetFocusedBrowsingContext(nullptr, aActionId
);
2260 Maybe
<AutoRestore
<RefPtr
<Element
>>> ar
;
2261 if (!mFirstBlurEvent
) {
2262 ar
.emplace(mFirstBlurEvent
);
2263 mFirstBlurEvent
= element
;
2266 nsPresContext
* focusedPresContext
=
2267 GetActiveBrowsingContext() ? presShell
->GetPresContext() : nullptr;
2268 IMEStateManager::OnChangeFocus(focusedPresContext
, nullptr,
2269 GetFocusMoveActionCause(0));
2271 // now adjust the actual focus, by clearing the fields in the focus manager
2272 // and in the window.
2273 mFocusedElement
= nullptr;
2274 if (aBrowsingContextToClear
) {
2275 nsPIDOMWindowOuter
* windowToClear
= aBrowsingContextToClear
->GetDOMWindow();
2276 if (windowToClear
) {
2277 windowToClear
->SetFocusedElement(nullptr);
2281 LOGCONTENT("Element %s has been blurred", element
.get());
2283 // Don't fire blur event on the root content which isn't editable.
2284 bool sendBlurEvent
=
2285 element
&& element
->IsInComposedDoc() && !IsNonFocusableRoot(element
);
2287 if (sendBlurEvent
) {
2288 NotifyFocusStateChange(element
, aElementToFocus
, 0, false, false);
2291 bool windowBeingLowered
= !aBrowsingContextToClear
&&
2292 !aAncestorBrowsingContextToFocus
&&
2293 aIsLeavingDocument
&& aAdjustWidget
;
2294 // if the object being blurred is a remote browser, deactivate remote
2296 if (BrowserParent
* remote
= BrowserParent::GetFrom(element
)) {
2297 MOZ_ASSERT(XRE_IsParentProcess());
2298 // Let's deactivate all remote browsers.
2299 BrowsingContext
* topLevelBrowsingContext
= remote
->GetBrowsingContext();
2300 topLevelBrowsingContext
->PreOrderWalk([&](BrowsingContext
* aContext
) {
2301 if (WindowGlobalParent
* windowGlobalParent
=
2302 aContext
->Canonical()->GetCurrentWindowGlobal()) {
2303 if (RefPtr
<BrowserParent
> browserParent
=
2304 windowGlobalParent
->GetBrowserParent()) {
2305 browserParent
->Deactivate(windowBeingLowered
, aActionId
);
2306 LOGFOCUS(("%s remote browser deactivated %p, %d",
2307 aContext
== topLevelBrowsingContext
? "Top-level"
2309 browserParent
.get(), windowBeingLowered
));
2315 // Same as above but for out-of-process iframes
2316 if (BrowserBridgeChild
* bbc
= BrowserBridgeChild::GetFrom(element
)) {
2317 bbc
->Deactivate(windowBeingLowered
, aActionId
);
2318 LOGFOCUS(("Out-of-process iframe deactivated %p, %d", bbc
,
2319 windowBeingLowered
));
2324 if (sendBlurEvent
) {
2325 // if there is an active window, update commands. If there isn't an active
2326 // window, then this was a blur caused by the active window being lowered,
2327 // so there is no need to update the commands
2328 if (GetActiveBrowsingContext()) {
2329 window
->UpdateCommands(u
"focus"_ns
, nullptr, 0);
2332 SendFocusOrBlurEvent(eBlur
, presShell
, element
->GetComposedDoc(), element
,
2333 1, false, false, aElementToFocus
);
2336 // if we are leaving the document or the window was lowered, make the caret
2338 if (aIsLeavingDocument
|| !GetActiveBrowsingContext()) {
2339 SetCaretVisible(presShell
, false, nullptr);
2342 RefPtr
<AccessibleCaretEventHub
> eventHub
=
2343 presShell
->GetAccessibleCaretEventHub();
2345 eventHub
->NotifyBlur(aIsLeavingDocument
|| !GetActiveBrowsingContext());
2348 // at this point, it is expected that this window will be still be
2349 // focused, but the focused element will be null, as it was cleared before
2350 // the event. If this isn't the case, then something else was focused during
2351 // the blur event above and we should just return. However, if
2352 // aIsLeavingDocument is set, a new document is desired, so make sure to
2353 // blur the document and window.
2354 if (GetFocusedBrowsingContext() != window
->GetBrowsingContext() ||
2355 (mFocusedElement
!= nullptr && !aIsLeavingDocument
)) {
2357 } else if (aIsLeavingDocument
) {
2358 window
->TakeFocus(false, 0);
2360 // clear the focus so that the ancestor frame hierarchy is in the correct
2361 // state. Pass true because aAncestorBrowsingContextToFocus is thought to be
2362 // focused at this point.
2363 if (aAncestorBrowsingContextToFocus
) {
2364 nsPIDOMWindowOuter
* ancestorWindowToFocus
=
2365 aAncestorBrowsingContextToFocus
->GetDOMWindow();
2366 if (ancestorWindowToFocus
) {
2367 ancestorWindowToFocus
->SetFocusedElement(nullptr, 0, true);
2371 SetFocusedWindowInternal(nullptr, aActionId
);
2372 mFocusedElement
= nullptr;
2374 // pass 1 for the focus method when calling SendFocusOrBlurEvent just so
2375 // that the check is made for suppressed documents. Check to ensure that
2376 // the document isn't null in case someone closed it during the blur above
2377 Document
* doc
= window
->GetExtantDoc();
2379 SendFocusOrBlurEvent(eBlur
, presShell
, doc
, ToSupports(doc
), 1, false);
2381 if (!GetFocusedBrowsingContext()) {
2382 SendFocusOrBlurEvent(eBlur
, presShell
, doc
,
2383 window
->GetCurrentInnerWindow(), 1, false);
2386 // check if a different window was focused
2387 result
= (!GetFocusedBrowsingContext() && GetActiveBrowsingContext());
2388 } else if (GetActiveBrowsingContext()) {
2389 // Otherwise, the blur of the element without blurring the document
2390 // occurred normally. Call UpdateCaret to redisplay the caret at the right
2391 // location within the document. This is needed to ensure that the caret
2392 // used for caret browsing is made visible again when an input field is
2394 UpdateCaret(false, true, nullptr);
2400 void nsFocusManager::ActivateRemoteFrameIfNeeded(Element
& aElement
,
2401 uint64_t aActionId
) {
2402 if (BrowserParent
* remote
= BrowserParent::GetFrom(&aElement
)) {
2403 remote
->Activate(aActionId
);
2404 LOGFOCUS(("Remote browser activated %p", remote
));
2407 // Same as above but for out-of-process iframes
2408 if (BrowserBridgeChild
* bbc
= BrowserBridgeChild::GetFrom(&aElement
)) {
2409 bbc
->Activate(aActionId
);
2410 LOGFOCUS(("Out-of-process iframe activated %p", bbc
));
2414 void nsFocusManager::Focus(
2415 nsPIDOMWindowOuter
* aWindow
, Element
* aElement
, uint32_t aFlags
,
2416 bool aIsNewDocument
, bool aFocusChanged
, bool aWindowRaised
,
2417 bool aAdjustWidget
, uint64_t aActionId
,
2418 const Maybe
<BlurredElementInfo
>& aBlurredElementInfo
) {
2419 LOGFOCUS(("<<Focus begin>>"));
2426 (aElement
== mFirstFocusEvent
|| aElement
== mFirstBlurEvent
)) {
2430 // Keep a reference to the presShell since dispatching the DOM event may
2431 // cause the document to be destroyed.
2432 nsCOMPtr
<nsIDocShell
> docShell
= aWindow
->GetDocShell();
2437 RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
2442 bool focusInOtherContentProcess
= false;
2443 // Keep mochitest-browser-chrome harness happy by ignoring
2444 // focusInOtherContentProcess in the chrome process, because the harness
2446 if (!XRE_IsParentProcess()) {
2447 if (RefPtr
<nsFrameLoaderOwner
> flo
= do_QueryObject(aElement
)) {
2448 // Only look at pre-existing browsing contexts. If this function is
2449 // called during reflow, calling GetBrowsingContext() could cause frame
2450 // loader initialization at a time when it isn't safe.
2451 if (BrowsingContext
* bc
= flo
->GetExtantBrowsingContext()) {
2452 focusInOtherContentProcess
= !bc
->IsInProcess();
2456 if (ActionIdComparableAndLower(
2457 aActionId
, mActionIdForFocusedBrowsingContextInContent
)) {
2458 // Unclear if this ever happens.
2460 ("Ignored an attempt to focus an element due to stale action id."));
2465 // If the focus actually changed, set the focus method (mouse, keyboard, etc).
2466 // Otherwise, just get the current focus method and use that. This ensures
2467 // that the method is set during the document and window focus events.
2468 uint32_t focusMethod
= aFocusChanged
2469 ? aFlags
& FOCUSMETHODANDRING_MASK
2470 : aWindow
->GetFocusMethod() |
2471 (aFlags
& (FLAG_SHOWRING
| FLAG_NOSHOWRING
));
2473 if (!IsWindowVisible(aWindow
)) {
2474 // if the window isn't visible, for instance because it is a hidden tab,
2475 // update the current focus and scroll it into view but don't do anything
2477 if (FlushAndCheckIfFocusable(aElement
, aFlags
)) {
2478 aWindow
->SetFocusedElement(aElement
, focusMethod
);
2479 if (aFocusChanged
) {
2480 ScrollIntoView(presShell
, aElement
, aFlags
);
2486 Maybe
<AutoRestore
<RefPtr
<Element
>>> ar
;
2487 if (!mFirstFocusEvent
) {
2488 ar
.emplace(mFirstFocusEvent
);
2489 mFirstFocusEvent
= aElement
;
2492 LOGCONTENT("Element %s has been focused", aElement
);
2494 if (MOZ_LOG_TEST(gFocusLog
, LogLevel::Debug
)) {
2495 Document
* docm
= aWindow
->GetExtantDoc();
2497 LOGCONTENT(" from %s", docm
->GetRootElement());
2499 LOGFOCUS((" [Newdoc: %d FocusChanged: %d Raised: %d Flags: %x]",
2500 aIsNewDocument
, aFocusChanged
, aWindowRaised
, aFlags
));
2503 if (aIsNewDocument
) {
2504 // if this is a new document, update the parent chain of frames so that
2505 // focus can be traversed from the top level down to the newly focused
2507 AdjustWindowFocus(aWindow
->GetBrowsingContext(), false,
2508 IsWindowVisible(aWindow
), aActionId
);
2511 // indicate that the window has taken focus.
2512 if (aWindow
->TakeFocus(true, focusMethod
)) {
2513 aIsNewDocument
= true;
2516 SetFocusedWindowInternal(aWindow
, aActionId
);
2518 if (aAdjustWidget
&& !sTestMode
) {
2519 if (nsViewManager
* vm
= presShell
->GetViewManager()) {
2520 nsCOMPtr
<nsIWidget
> widget
;
2521 vm
->GetRootWidget(getter_AddRefs(widget
));
2523 widget
->SetFocus(nsIWidget::Raise::No
, aFlags
& FLAG_NONSYSTEMCALLER
2524 ? CallerType::NonSystem
2525 : CallerType::System
);
2529 // if switching to a new document, first fire the focus event on the
2530 // document and then the window.
2531 if (aIsNewDocument
) {
2532 Document
* doc
= aWindow
->GetExtantDoc();
2533 // The focus change should be notified to IMEStateManager from here if
2534 // the focused element is a designMode editor since any content won't
2535 // receive focus event.
2536 if (doc
&& doc
->HasFlag(NODE_IS_EDITABLE
)) {
2537 IMEStateManager::OnChangeFocus(presShell
->GetPresContext(), nullptr,
2538 GetFocusMoveActionCause(aFlags
));
2540 if (doc
&& !focusInOtherContentProcess
) {
2541 SendFocusOrBlurEvent(eFocus
, presShell
, doc
, ToSupports(doc
),
2542 aFlags
& FOCUSMETHOD_MASK
, aWindowRaised
);
2544 if (GetFocusedBrowsingContext() == aWindow
->GetBrowsingContext() &&
2545 !mFocusedElement
&& !focusInOtherContentProcess
) {
2546 SendFocusOrBlurEvent(eFocus
, presShell
, doc
,
2547 aWindow
->GetCurrentInnerWindow(),
2548 aFlags
& FOCUSMETHOD_MASK
, aWindowRaised
);
2552 // check to ensure that the element is still focusable, and that nothing
2553 // else was focused during the events above.
2554 if (FlushAndCheckIfFocusable(aElement
, aFlags
) &&
2555 GetFocusedBrowsingContext() == aWindow
->GetBrowsingContext() &&
2556 mFocusedElement
== nullptr) {
2557 mFocusedElement
= aElement
;
2559 nsIContent
* focusedNode
= aWindow
->GetFocusedElement();
2560 const bool sendFocusEvent
= aElement
&& aElement
->IsInComposedDoc() &&
2561 !IsNonFocusableRoot(aElement
);
2562 const bool isRefocus
= focusedNode
&& focusedNode
== aElement
;
2563 const bool shouldShowFocusRing
=
2564 sendFocusEvent
&& ShouldMatchFocusVisible(aWindow
, *aElement
, aFlags
);
2566 aWindow
->SetFocusedElement(aElement
, focusMethod
, false);
2568 // if the focused element changed, scroll it into view
2569 if (aElement
&& aFocusChanged
) {
2570 ScrollIntoView(presShell
, aElement
, aFlags
);
2572 nsPresContext
* presContext
= presShell
->GetPresContext();
2573 if (sendFocusEvent
) {
2574 NotifyFocusStateChange(aElement
, nullptr, aFlags
,
2575 /* aGettingFocus = */ true, shouldShowFocusRing
);
2577 // If this is a remote browser, focus its widget and activate remote
2578 // content. Note that we might no longer be in the same document,
2579 // due to the events we fired above when aIsNewDocument.
2580 if (presShell
->GetDocument() == aElement
->GetComposedDoc()) {
2581 ActivateRemoteFrameIfNeeded(*aElement
, aActionId
);
2584 IMEStateManager::OnChangeFocus(presContext
, aElement
,
2585 GetFocusMoveActionCause(aFlags
));
2587 // as long as this focus wasn't because a window was raised, update the
2589 // XXXndeakin P2 someone could adjust the focus during the update
2590 if (!aWindowRaised
) {
2591 aWindow
->UpdateCommands(u
"focus"_ns
, nullptr, 0);
2594 if (!focusInOtherContentProcess
) {
2595 SendFocusOrBlurEvent(
2596 eFocus
, presShell
, aElement
->GetComposedDoc(), aElement
,
2597 aFlags
& FOCUSMETHOD_MASK
, aWindowRaised
, isRefocus
,
2598 aBlurredElementInfo
? aBlurredElementInfo
->mElement
.get()
2602 IMEStateManager::OnChangeFocus(presContext
, nullptr,
2603 GetFocusMoveActionCause(aFlags
));
2604 if (!aWindowRaised
) {
2605 aWindow
->UpdateCommands(u
"focus"_ns
, nullptr, 0);
2609 if (!mFocusedElement
) {
2610 // When there is no focused element, IMEStateManager needs to adjust IME
2611 // enabled state with the document.
2612 nsPresContext
* presContext
= presShell
->GetPresContext();
2613 IMEStateManager::OnChangeFocus(presContext
, nullptr,
2614 GetFocusMoveActionCause(aFlags
));
2617 if (!aWindowRaised
) {
2618 aWindow
->UpdateCommands(u
"focus"_ns
, nullptr, 0);
2622 // update the caret visibility and position to match the newly focused
2623 // element. However, don't update the position if this was a focus due to a
2624 // mouse click as the selection code would already have moved the caret as
2625 // needed. If this is a different document than was focused before, also
2626 // update the caret's visibility. If this is the same document, the caret
2627 // visibility should be the same as before so there is no need to update it.
2628 if (mFocusedElement
== aElement
)
2629 UpdateCaret(aFocusChanged
&& !(aFlags
& FLAG_BYMOUSE
), aIsNewDocument
,
2633 class FocusBlurEvent
: public Runnable
{
2635 FocusBlurEvent(nsISupports
* aTarget
, EventMessage aEventMessage
,
2636 nsPresContext
* aContext
, bool aWindowRaised
, bool aIsRefocus
,
2637 EventTarget
* aRelatedTarget
)
2638 : mozilla::Runnable("FocusBlurEvent"),
2641 mEventMessage(aEventMessage
),
2642 mWindowRaised(aWindowRaised
),
2643 mIsRefocus(aIsRefocus
),
2644 mRelatedTarget(aRelatedTarget
) {}
2646 NS_IMETHOD
Run() override
{
2647 InternalFocusEvent
event(true, mEventMessage
);
2648 event
.mFlags
.mBubbles
= false;
2649 event
.mFlags
.mCancelable
= false;
2650 event
.mFromRaise
= mWindowRaised
;
2651 event
.mIsRefocus
= mIsRefocus
;
2652 event
.mRelatedTarget
= mRelatedTarget
;
2653 return EventDispatcher::Dispatch(mTarget
, mContext
, &event
);
2656 nsCOMPtr
<nsISupports
> mTarget
;
2657 RefPtr
<nsPresContext
> mContext
;
2658 EventMessage mEventMessage
;
2661 nsCOMPtr
<EventTarget
> mRelatedTarget
;
2664 class FocusInOutEvent
: public Runnable
{
2666 FocusInOutEvent(nsISupports
* aTarget
, EventMessage aEventMessage
,
2667 nsPresContext
* aContext
,
2668 nsPIDOMWindowOuter
* aOriginalFocusedWindow
,
2669 nsIContent
* aOriginalFocusedContent
,
2670 EventTarget
* aRelatedTarget
)
2671 : mozilla::Runnable("FocusInOutEvent"),
2674 mEventMessage(aEventMessage
),
2675 mOriginalFocusedWindow(aOriginalFocusedWindow
),
2676 mOriginalFocusedContent(aOriginalFocusedContent
),
2677 mRelatedTarget(aRelatedTarget
) {}
2679 NS_IMETHOD
Run() override
{
2680 nsCOMPtr
<nsIContent
> originalWindowFocus
=
2681 mOriginalFocusedWindow
? mOriginalFocusedWindow
->GetFocusedElement()
2683 // Blink does not check that focus is the same after blur, but WebKit does.
2684 // Opt to follow Blink's behavior (see bug 687787).
2685 if (mEventMessage
== eFocusOut
||
2686 originalWindowFocus
== mOriginalFocusedContent
) {
2687 InternalFocusEvent
event(true, mEventMessage
);
2688 event
.mFlags
.mBubbles
= true;
2689 event
.mFlags
.mCancelable
= false;
2690 event
.mRelatedTarget
= mRelatedTarget
;
2691 return EventDispatcher::Dispatch(mTarget
, mContext
, &event
);
2696 nsCOMPtr
<nsISupports
> mTarget
;
2697 RefPtr
<nsPresContext
> mContext
;
2698 EventMessage mEventMessage
;
2699 nsCOMPtr
<nsPIDOMWindowOuter
> mOriginalFocusedWindow
;
2700 nsCOMPtr
<nsIContent
> mOriginalFocusedContent
;
2701 nsCOMPtr
<EventTarget
> mRelatedTarget
;
2704 static Document
* GetDocumentHelper(EventTarget
* aTarget
) {
2705 nsCOMPtr
<nsINode
> node
= do_QueryInterface(aTarget
);
2707 nsCOMPtr
<nsPIDOMWindowInner
> win
= do_QueryInterface(aTarget
);
2708 return win
? win
->GetExtantDoc() : nullptr;
2711 return node
->OwnerDoc();
2714 void nsFocusManager::FireFocusInOrOutEvent(
2715 EventMessage aEventMessage
, PresShell
* aPresShell
, nsISupports
* aTarget
,
2716 nsPIDOMWindowOuter
* aCurrentFocusedWindow
,
2717 nsIContent
* aCurrentFocusedContent
, EventTarget
* aRelatedTarget
) {
2718 NS_ASSERTION(aEventMessage
== eFocusIn
|| aEventMessage
== eFocusOut
,
2719 "Wrong event type for FireFocusInOrOutEvent");
2721 nsContentUtils::AddScriptRunner(new FocusInOutEvent(
2722 aTarget
, aEventMessage
, aPresShell
->GetPresContext(),
2723 aCurrentFocusedWindow
, aCurrentFocusedContent
, aRelatedTarget
));
2726 void nsFocusManager::SendFocusOrBlurEvent(
2727 EventMessage aEventMessage
, PresShell
* aPresShell
, Document
* aDocument
,
2728 nsISupports
* aTarget
, uint32_t aFocusMethod
, bool aWindowRaised
,
2729 bool aIsRefocus
, EventTarget
* aRelatedTarget
) {
2730 NS_ASSERTION(aEventMessage
== eFocus
|| aEventMessage
== eBlur
,
2731 "Wrong event type for SendFocusOrBlurEvent");
2733 nsCOMPtr
<EventTarget
> eventTarget
= do_QueryInterface(aTarget
);
2734 nsCOMPtr
<Document
> eventTargetDoc
= GetDocumentHelper(eventTarget
);
2735 nsCOMPtr
<Document
> relatedTargetDoc
= GetDocumentHelper(aRelatedTarget
);
2737 // set aRelatedTarget to null if it's not in the same document as eventTarget
2738 if (eventTargetDoc
!= relatedTargetDoc
) {
2739 aRelatedTarget
= nullptr;
2742 if (aDocument
&& aDocument
->EventHandlingSuppressed()) {
2743 // if this event was already queued, remove it and append it to the end
2744 mDelayedBlurFocusEvents
.RemoveElementsBy([&](const auto& event
) {
2745 return event
.mEventMessage
== aEventMessage
&&
2746 event
.mPresShell
== aPresShell
&& event
.mDocument
== aDocument
&&
2747 event
.mTarget
== eventTarget
&&
2748 event
.mRelatedTarget
== aRelatedTarget
;
2751 mDelayedBlurFocusEvents
.EmplaceBack(aEventMessage
, aPresShell
, aDocument
,
2752 eventTarget
, aRelatedTarget
);
2756 // If mDelayedBlurFocusEvents queue is not empty, check if there are events
2757 // that belongs to this doc, if yes, fire them first.
2758 if (aDocument
&& !aDocument
->EventHandlingSuppressed() &&
2759 mDelayedBlurFocusEvents
.Length()) {
2760 FireDelayedEvents(aDocument
);
2763 FireFocusOrBlurEvent(aEventMessage
, aPresShell
, aTarget
, aWindowRaised
,
2764 aIsRefocus
, aRelatedTarget
);
2767 void nsFocusManager::FireFocusOrBlurEvent(EventMessage aEventMessage
,
2768 PresShell
* aPresShell
,
2769 nsISupports
* aTarget
,
2770 bool aWindowRaised
, bool aIsRefocus
,
2771 EventTarget
* aRelatedTarget
) {
2772 nsCOMPtr
<nsPIDOMWindowOuter
> currentWindow
= mFocusedWindow
;
2773 nsCOMPtr
<nsPIDOMWindowInner
> targetWindow
= do_QueryInterface(aTarget
);
2774 nsCOMPtr
<Document
> targetDocument
= do_QueryInterface(aTarget
);
2775 nsCOMPtr
<nsIContent
> currentFocusedContent
=
2776 currentWindow
? currentWindow
->GetFocusedElement() : nullptr;
2778 #ifdef ACCESSIBILITY
2779 nsAccessibilityService
* accService
= GetAccService();
2781 if (aEventMessage
== eFocus
) {
2782 accService
->NotifyOfDOMFocus(aTarget
);
2784 accService
->NotifyOfDOMBlur(aTarget
);
2789 nsContentUtils::AddScriptRunner(
2790 new FocusBlurEvent(aTarget
, aEventMessage
, aPresShell
->GetPresContext(),
2791 aWindowRaised
, aIsRefocus
, aRelatedTarget
));
2793 // Check that the target is not a window or document before firing
2794 // focusin/focusout. Other browsers do not fire focusin/focusout on window,
2795 // despite being required in the spec, so follow their behavior.
2797 // As for document, we should not even fire focus/blur, but until then, we
2798 // need this check. targetDocument should be removed once bug 1228802 is
2800 if (!targetWindow
&& !targetDocument
) {
2801 EventMessage focusInOrOutMessage
=
2802 aEventMessage
== eFocus
? eFocusIn
: eFocusOut
;
2803 FireFocusInOrOutEvent(focusInOrOutMessage
, aPresShell
, aTarget
,
2804 currentWindow
, currentFocusedContent
, aRelatedTarget
);
2808 void nsFocusManager::ScrollIntoView(PresShell
* aPresShell
, nsIContent
* aContent
,
2810 if (aFlags
& FLAG_NOSCROLL
) {
2813 // If the noscroll flag isn't set, scroll the newly focused element into view.
2814 aPresShell
->ScrollContentIntoView(
2815 aContent
, ScrollAxis(kScrollMinimum
, WhenToScroll::IfNotVisible
),
2816 ScrollAxis(kScrollMinimum
, WhenToScroll::IfNotVisible
),
2817 ScrollFlags::ScrollOverflowHidden
);
2818 // Scroll the input / textarea selection into view, unless focused with the
2819 // mouse, see bug 572649.
2820 if (aFlags
& FLAG_BYMOUSE
) {
2823 // ScrollContentIntoView flushes layout, so no need to flush again here.
2824 if (nsTextControlFrame
* tf
= do_QueryFrame(aContent
->GetPrimaryFrame())) {
2825 tf
->ScrollSelectionIntoViewAsync(nsTextControlFrame::ScrollAncestors::Yes
);
2829 void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter
* aWindow
,
2830 CallerType aCallerType
, uint64_t aActionId
) {
2831 // don't raise windows that are already raised or are in the process of
2834 if (!aWindow
|| aWindow
== mWindowBeingLowered
) {
2838 if (XRE_IsParentProcess()) {
2839 if (aWindow
== mActiveWindow
) {
2843 BrowsingContext
* bc
= aWindow
->GetBrowsingContext();
2844 // TODO: Deeper OOP frame hierarchies are
2845 // https://bugzilla.mozilla.org/show_bug.cgi?id=1661227
2846 if (bc
== GetActiveBrowsingContext()) {
2849 if (bc
== GetFocusedBrowsingContext()) {
2855 // In test mode, emulate raising the window. WindowRaised takes
2856 // care of lowering the present active window. This happens in
2857 // a separate runnable to avoid touching multiple windows in
2858 // the current runnable.
2860 nsCOMPtr
<nsPIDOMWindowOuter
> window(aWindow
);
2861 RefPtr
<nsFocusManager
> self(this);
2862 NS_DispatchToCurrentThread(NS_NewRunnableFunction(
2863 "nsFocusManager::RaiseWindow", [self
, window
, aActionId
]() -> void {
2864 self
->WindowRaised(window
, aActionId
);
2869 if (XRE_IsContentProcess()) {
2870 BrowsingContext
* bc
= aWindow
->GetBrowsingContext();
2872 // Assume the raise below will succeed and run the raising synchronously
2873 // in this process to make the focus event that is observable in this
2874 // process fire in the right order relative to mouseup when we are here
2875 // thanks to a mousedown.
2876 WindowRaised(aWindow
, aActionId
);
2881 // Windows would rather we focus the child widget, otherwise, the toplevel
2882 // widget will always end up being focused. Fortunately, focusing the child
2883 // widget will also have the effect of raising the window this widget is in.
2884 // But on other platforms, we can just focus the toplevel widget to raise
2886 nsCOMPtr
<nsPIDOMWindowOuter
> childWindow
;
2887 GetFocusedDescendant(aWindow
, eIncludeAllDescendants
,
2888 getter_AddRefs(childWindow
));
2890 childWindow
= aWindow
;
2893 nsCOMPtr
<nsIDocShell
> docShell
= aWindow
->GetDocShell();
2898 PresShell
* presShell
= docShell
->GetPresShell();
2903 if (nsViewManager
* vm
= presShell
->GetViewManager()) {
2904 nsCOMPtr
<nsIWidget
> widget
;
2905 vm
->GetRootWidget(getter_AddRefs(widget
));
2907 widget
->SetFocus(nsIWidget::Raise::Yes
, aCallerType
);
2911 nsCOMPtr
<nsIBaseWindow
> treeOwnerAsWin
=
2912 do_QueryInterface(aWindow
->GetDocShell());
2913 if (treeOwnerAsWin
) {
2914 nsCOMPtr
<nsIWidget
> widget
;
2915 treeOwnerAsWin
->GetMainWidget(getter_AddRefs(widget
));
2917 widget
->SetFocus(nsIWidget::Raise::Yes
, aCallerType
);
2923 void nsFocusManager::UpdateCaretForCaretBrowsingMode() {
2924 UpdateCaret(false, true, mFocusedElement
);
2927 void nsFocusManager::UpdateCaret(bool aMoveCaretToFocus
, bool aUpdateVisibility
,
2928 nsIContent
* aContent
) {
2929 LOGFOCUS(("Update Caret: %d %d", aMoveCaretToFocus
, aUpdateVisibility
));
2931 if (!mFocusedWindow
) {
2935 // this is called when a document is focused or when the caretbrowsing
2936 // preference is changed
2937 nsCOMPtr
<nsIDocShell
> focusedDocShell
= mFocusedWindow
->GetDocShell();
2938 if (!focusedDocShell
) {
2942 if (focusedDocShell
->ItemType() == nsIDocShellTreeItem::typeChrome
) {
2943 return; // Never browse with caret in chrome
2946 bool browseWithCaret
= Preferences::GetBool("accessibility.browsewithcaret");
2948 RefPtr
<PresShell
> presShell
= focusedDocShell
->GetPresShell();
2953 // If this is an editable document which isn't contentEditable, or a
2954 // contentEditable document and the node to focus is contentEditable,
2955 // return, so that we don't mess with caret visibility.
2956 bool isEditable
= false;
2957 focusedDocShell
->GetEditable(&isEditable
);
2960 Document
* doc
= presShell
->GetDocument();
2962 bool isContentEditableDoc
=
2964 doc
->GetEditingState() == Document::EditingState::eContentEditable
;
2966 bool isFocusEditable
= aContent
&& aContent
->HasFlag(NODE_IS_EDITABLE
);
2967 if (!isContentEditableDoc
|| isFocusEditable
) {
2972 if (!isEditable
&& aMoveCaretToFocus
) {
2973 MoveCaretToFocus(presShell
, aContent
);
2976 if (!aUpdateVisibility
) {
2980 // XXXndeakin this doesn't seem right. It should be checking for this only
2981 // on the nearest ancestor frame which is a chrome frame. But this is
2982 // what the existing code does, so just leave it for now.
2983 if (!browseWithCaret
) {
2984 nsCOMPtr
<Element
> docElement
= mFocusedWindow
->GetFrameElementInternal();
2986 browseWithCaret
= docElement
->AttrValueIs(
2987 kNameSpaceID_None
, nsGkAtoms::showcaret
, u
"true"_ns
, eCaseMatters
);
2990 SetCaretVisible(presShell
, browseWithCaret
, aContent
);
2993 void nsFocusManager::MoveCaretToFocus(PresShell
* aPresShell
,
2994 nsIContent
* aContent
) {
2995 nsCOMPtr
<Document
> doc
= aPresShell
->GetDocument();
2997 RefPtr
<nsFrameSelection
> frameSelection
= aPresShell
->FrameSelection();
2998 RefPtr
<Selection
> domSelection
=
2999 frameSelection
->GetSelection(SelectionType::eNormal
);
3001 // First clear the selection. This way, if there is no currently focused
3002 // content, the selection will just be cleared.
3003 domSelection
->RemoveAllRanges(IgnoreErrors());
3006 RefPtr
<nsRange
> newRange
= doc
->CreateRange(rv
);
3007 if (NS_WARN_IF(rv
.Failed())) {
3008 rv
.SuppressException();
3012 // Set the range to the start of the currently focused node
3013 // Make sure it's collapsed
3014 newRange
->SelectNodeContents(*aContent
, IgnoreErrors());
3016 if (!aContent
->GetFirstChild() ||
3017 aContent
->IsNodeOfType(nsINode::eHTML_FORM_CONTROL
)) {
3018 // If current focus node is a leaf, set range to before the
3019 // node by using the parent as a container.
3020 // This prevents it from appearing as selected.
3021 newRange
->SetStartBefore(*aContent
, IgnoreErrors());
3022 newRange
->SetEndBefore(*aContent
, IgnoreErrors());
3024 domSelection
->AddRangeAndSelectFramesAndNotifyListeners(*newRange
,
3026 domSelection
->CollapseToStart(IgnoreErrors());
3032 nsresult
nsFocusManager::SetCaretVisible(PresShell
* aPresShell
, bool aVisible
,
3033 nsIContent
* aContent
) {
3034 // When browsing with caret, make sure caret is visible after new focus
3035 // Return early if there is no caret. This can happen for the testcase
3036 // for bug 308025 where a window is closed in a blur handler.
3037 RefPtr
<nsCaret
> caret
= aPresShell
->GetCaret();
3042 bool caretVisible
= caret
->IsVisible();
3043 if (!aVisible
&& !caretVisible
) {
3047 RefPtr
<nsFrameSelection
> frameSelection
;
3049 NS_ASSERTION(aContent
->GetComposedDoc() == aPresShell
->GetDocument(),
3051 nsIFrame
* focusFrame
= aContent
->GetPrimaryFrame();
3053 frameSelection
= focusFrame
->GetFrameSelection();
3057 RefPtr
<nsFrameSelection
> docFrameSelection
= aPresShell
->FrameSelection();
3059 if (docFrameSelection
&& caret
&&
3060 (frameSelection
== docFrameSelection
|| !aContent
)) {
3061 Selection
* domSelection
=
3062 docFrameSelection
->GetSelection(SelectionType::eNormal
);
3064 // First, hide the caret to prevent attempting to show it in
3065 // SetCaretDOMSelection
3066 aPresShell
->SetCaretEnabled(false);
3068 // Caret must blink on non-editable elements
3069 caret
->SetIgnoreUserModify(true);
3070 // Tell the caret which selection to use
3071 caret
->SetSelection(domSelection
);
3073 // In content, we need to set the caret. The only special case is edit
3074 // fields, which have a different frame selection from the document.
3075 // They will take care of making the caret visible themselves.
3077 aPresShell
->SetCaretReadOnly(false);
3078 aPresShell
->SetCaretEnabled(aVisible
);
3085 nsresult
nsFocusManager::GetSelectionLocation(Document
* aDocument
,
3086 PresShell
* aPresShell
,
3087 nsIContent
** aStartContent
,
3088 nsIContent
** aEndContent
) {
3089 *aStartContent
= *aEndContent
= nullptr;
3090 nsPresContext
* presContext
= aPresShell
->GetPresContext();
3091 NS_ASSERTION(presContext
, "mPresContent is null!!");
3093 RefPtr
<nsFrameSelection
> frameSelection
= aPresShell
->FrameSelection();
3095 RefPtr
<Selection
> domSelection
;
3096 if (frameSelection
) {
3097 domSelection
= frameSelection
->GetSelection(SelectionType::eNormal
);
3100 bool isCollapsed
= false;
3101 nsCOMPtr
<nsIContent
> startContent
, endContent
;
3102 uint32_t startOffset
= 0;
3104 isCollapsed
= domSelection
->IsCollapsed();
3105 RefPtr
<const nsRange
> domRange
= domSelection
->GetRangeAt(0);
3107 nsCOMPtr
<nsINode
> startNode
= domRange
->GetStartContainer();
3108 nsCOMPtr
<nsINode
> endNode
= domRange
->GetEndContainer();
3109 startOffset
= domRange
->StartOffset();
3111 nsIContent
* childContent
= nullptr;
3113 startContent
= do_QueryInterface(startNode
);
3114 if (startContent
&& startContent
->IsElement()) {
3115 childContent
= startContent
->GetChildAt_Deprecated(startOffset
);
3117 startContent
= childContent
;
3121 endContent
= do_QueryInterface(endNode
);
3122 if (endContent
&& endContent
->IsElement()) {
3123 uint32_t endOffset
= domRange
->EndOffset();
3124 childContent
= endContent
->GetChildAt_Deprecated(endOffset
);
3126 endContent
= childContent
;
3131 return NS_ERROR_INVALID_ARG
;
3134 nsIFrame
* startFrame
= nullptr;
3136 startFrame
= startContent
->GetPrimaryFrame();
3138 // Next check to see if our caret is at the very end of a node
3139 // If so, the caret is actually sitting in front of the next
3140 // logical frame's primary node - so for this case we need to
3141 // change caretContent to that node.
3143 if (startContent
->NodeType() == nsINode::TEXT_NODE
) {
3144 nsAutoString nodeValue
;
3145 startContent
->GetAsText()->AppendTextTo(nodeValue
);
3147 bool isFormControl
=
3148 startContent
->IsNodeOfType(nsINode::eHTML_FORM_CONTROL
);
3150 if (nodeValue
.Length() == startOffset
&& !isFormControl
&&
3151 startContent
!= aDocument
->GetRootElement()) {
3152 // Yes, indeed we were at the end of the last node
3153 nsCOMPtr
<nsIFrameEnumerator
> frameTraversal
;
3154 nsresult rv
= NS_NewFrameTraversal(getter_AddRefs(frameTraversal
),
3155 presContext
, startFrame
, eLeaf
,
3157 false, // aLockInScrollView
3158 true, // aFollowOOFs
3159 false // aSkipPopupChecks
3161 NS_ENSURE_SUCCESS(rv
, rv
);
3163 nsIFrame
* newCaretFrame
= nullptr;
3164 nsCOMPtr
<nsIContent
> newCaretContent
= startContent
;
3165 bool endOfSelectionInStartNode(startContent
== endContent
);
3167 // Continue getting the next frame until the primary content for the
3168 // frame we are on changes - we don't want to be stuck in the same
3170 frameTraversal
->Next();
3172 static_cast<nsIFrame
*>(frameTraversal
->CurrentItem());
3173 if (nullptr == newCaretFrame
) break;
3174 newCaretContent
= newCaretFrame
->GetContent();
3175 } while (!newCaretContent
|| newCaretContent
== startContent
);
3177 if (newCaretFrame
&& newCaretContent
) {
3178 // If the caret is exactly at the same position of the new frame,
3179 // then we can use the newCaretFrame and newCaretContent for our
3182 nsIFrame
* frame
= nsCaret::GetGeometry(domSelection
, &caretRect
);
3184 nsPoint caretWidgetOffset
;
3185 nsIWidget
* widget
= frame
->GetNearestWidget(caretWidgetOffset
);
3186 caretRect
.MoveBy(caretWidgetOffset
);
3187 nsPoint newCaretOffset
;
3188 nsIWidget
* newCaretWidget
=
3189 newCaretFrame
->GetNearestWidget(newCaretOffset
);
3190 if (widget
== newCaretWidget
&& caretRect
.y
== newCaretOffset
.y
&&
3191 caretRect
.x
== newCaretOffset
.x
) {
3192 // The caret is at the start of the new element.
3193 startFrame
= newCaretFrame
;
3194 startContent
= newCaretContent
;
3195 if (endOfSelectionInStartNode
) {
3196 endContent
= newCaretContent
; // Ensure end of selection is
3207 *aStartContent
= startContent
;
3208 *aEndContent
= endContent
;
3209 NS_IF_ADDREF(*aStartContent
);
3210 NS_IF_ADDREF(*aEndContent
);
3215 nsresult
nsFocusManager::DetermineElementToMoveFocus(
3216 nsPIDOMWindowOuter
* aWindow
, nsIContent
* aStartContent
, int32_t aType
,
3217 bool aNoParentTraversal
, bool aNavigateByKey
, nsIContent
** aNextContent
) {
3218 *aNextContent
= nullptr;
3220 // This is used for document navigation only. It will be set to true if we
3221 // start navigating from a starting point. If this starting point is near the
3222 // end of the document (for example, an element on a statusbar), and there
3223 // are no child documents or panels before the end of the document, then we
3224 // will need to ensure that we don't consider the root chrome window when we
3225 // loop around and instead find the next child document/panel, as focus is
3226 // already in that window. This flag will be cleared once we navigate into
3227 // another document.
3228 bool mayFocusRoot
= (aStartContent
!= nullptr);
3230 nsCOMPtr
<nsIContent
> startContent
= aStartContent
;
3231 if (!startContent
&& aType
!= MOVEFOCUS_CARET
) {
3232 if (aType
== MOVEFOCUS_FORWARDDOC
|| aType
== MOVEFOCUS_BACKWARDDOC
) {
3233 // When moving between documents, make sure to get the right
3234 // starting content in a descendant.
3235 nsCOMPtr
<nsPIDOMWindowOuter
> focusedWindow
;
3236 startContent
= GetFocusedDescendant(aWindow
, eIncludeAllDescendants
,
3237 getter_AddRefs(focusedWindow
));
3238 } else if (aType
!= MOVEFOCUS_LASTDOC
) {
3239 // Otherwise, start at the focused node. If MOVEFOCUS_LASTDOC is used,
3240 // then we are document-navigating backwards from chrome to the content
3241 // process, and we don't want to use this so that we start from the end
3243 startContent
= aWindow
->GetFocusedElement();
3247 nsCOMPtr
<Document
> doc
;
3249 doc
= startContent
->GetComposedDoc();
3251 doc
= aWindow
->GetExtantDoc();
3252 if (!doc
) return NS_OK
;
3254 LookAndFeel::GetInt(LookAndFeel::IntID::TabFocusModel
,
3255 &nsIContent::sTabFocusModel
);
3257 // True if we are navigating by document (F6/Shift+F6) or false if we are
3258 // navigating by element (Tab/Shift+Tab).
3259 const bool forDocumentNavigation
=
3260 aType
== MOVEFOCUS_FORWARDDOC
|| aType
== MOVEFOCUS_BACKWARDDOC
||
3261 aType
== MOVEFOCUS_FIRSTDOC
|| aType
== MOVEFOCUS_LASTDOC
;
3263 // If moving to the root or first document, find the root element and return.
3264 if (aType
== MOVEFOCUS_ROOT
|| aType
== MOVEFOCUS_FIRSTDOC
) {
3265 NS_IF_ADDREF(*aNextContent
= GetRootForFocus(aWindow
, doc
, false, false));
3266 if (!*aNextContent
&& aType
== MOVEFOCUS_FIRSTDOC
) {
3267 // When looking for the first document, if the root wasn't focusable,
3268 // find the next focusable document.
3269 aType
= MOVEFOCUS_FORWARDDOC
;
3275 Element
* rootContent
= doc
->GetRootElement();
3276 NS_ENSURE_TRUE(rootContent
, NS_OK
);
3278 PresShell
* presShell
= doc
->GetPresShell();
3279 NS_ENSURE_TRUE(presShell
, NS_OK
);
3281 if (aType
== MOVEFOCUS_FIRST
) {
3282 if (!aStartContent
) {
3283 startContent
= rootContent
;
3285 return GetNextTabbableContent(presShell
, startContent
, nullptr,
3286 startContent
, true, 1, false, false,
3287 aNavigateByKey
, aNextContent
);
3289 if (aType
== MOVEFOCUS_LAST
) {
3290 if (!aStartContent
) {
3291 startContent
= rootContent
;
3293 return GetNextTabbableContent(presShell
, startContent
, nullptr,
3294 startContent
, false, 0, false, false,
3295 aNavigateByKey
, aNextContent
);
3298 bool forward
= (aType
== MOVEFOCUS_FORWARD
|| aType
== MOVEFOCUS_FORWARDDOC
||
3299 aType
== MOVEFOCUS_CARET
);
3300 bool doNavigation
= true;
3301 bool ignoreTabIndex
= false;
3302 // when a popup is open, we want to ensure that tab navigation occurs only
3303 // within the most recently opened panel. If a popup is open, its frame will
3304 // be stored in popupFrame.
3305 nsIFrame
* popupFrame
= nullptr;
3307 int32_t tabIndex
= forward
? 1 : 0;
3309 nsIFrame
* frame
= startContent
->GetPrimaryFrame();
3310 if (startContent
->IsHTMLElement(nsGkAtoms::area
)) {
3311 startContent
->IsFocusable(&tabIndex
);
3313 tabIndex
= frame
->IsFocusable().mTabIndex
;
3315 startContent
->IsFocusable(&tabIndex
);
3318 // if the current element isn't tabbable, ignore the tabindex and just
3319 // look for the next element. The root content won't have a tabindex
3320 // so just treat this as the beginning of the tab order.
3323 if (startContent
!= rootContent
) {
3324 ignoreTabIndex
= true;
3328 // check if the focus is currently inside a popup. Elements such as the
3329 // autocomplete widget use the noautofocus attribute to allow the focus to
3330 // remain outside the popup when it is opened.
3332 popupFrame
= nsLayoutUtils::GetClosestFrameOfType(
3333 frame
, LayoutFrameType::MenuPopup
);
3336 if (popupFrame
&& !forDocumentNavigation
) {
3337 // Don't navigate outside of a popup, so pretend that the
3338 // root content is the popup itself
3339 rootContent
= popupFrame
->GetContent()->AsElement();
3340 NS_ASSERTION(rootContent
, "Popup frame doesn't have a content node");
3341 } else if (!forward
) {
3342 // If focus moves backward and when current focused node is root
3343 // content or <body> element which is editable by contenteditable
3344 // attribute, focus should move to its parent document.
3345 if (startContent
== rootContent
) {
3346 doNavigation
= false;
3348 Document
* doc
= startContent
->GetComposedDoc();
3350 nsLayoutUtils::GetEditableRootContentByContentEditable(doc
)) {
3351 doNavigation
= false;
3357 if (aType
!= MOVEFOCUS_CARET
) {
3358 // if there is no focus, yet a panel is open, focus the first item in
3360 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
3362 popupFrame
= pm
->GetTopPopup(ePopupTypePanel
);
3367 // When there is a popup open, and no starting content, start the search
3368 // at the topmost popup.
3369 startContent
= popupFrame
->GetContent();
3370 NS_ASSERTION(startContent
, "Popup frame doesn't have a content node");
3371 // Unless we are searching for documents, set the root content to the
3372 // popup as well, so that we don't tab-navigate outside the popup.
3373 // When navigating by documents, we start at the popup but can navigate
3374 // outside of it to look for other panels and documents.
3375 if (!forDocumentNavigation
) {
3376 rootContent
= startContent
->AsElement();
3379 doc
= startContent
? startContent
->GetComposedDoc() : nullptr;
3381 // Otherwise, for content shells, start from the location of the caret.
3382 nsCOMPtr
<nsIDocShell
> docShell
= aWindow
->GetDocShell();
3383 if (docShell
&& docShell
->ItemType() != nsIDocShellTreeItem::typeChrome
) {
3384 nsCOMPtr
<nsIContent
> endSelectionContent
;
3385 GetSelectionLocation(doc
, presShell
, getter_AddRefs(startContent
),
3386 getter_AddRefs(endSelectionContent
));
3387 // If the selection is on the rootContent, then there is no selection
3388 if (startContent
== rootContent
) {
3389 startContent
= nullptr;
3392 if (aType
== MOVEFOCUS_CARET
) {
3393 // GetFocusInSelection finds a focusable link near the caret.
3394 // If there is no start content though, don't do this to avoid
3395 // focusing something unexpected.
3397 GetFocusInSelection(aWindow
, startContent
, endSelectionContent
,
3404 // when starting from a selection, we always want to find the next or
3405 // previous element in the document. So the tabindex on elements
3406 // should be ignored.
3407 ignoreTabIndex
= true;
3411 if (!startContent
) {
3412 // otherwise, just use the root content as the starting point
3413 startContent
= rootContent
;
3414 NS_ENSURE_TRUE(startContent
, NS_OK
);
3419 // Check if the starting content is the same as the content assigned to the
3420 // retargetdocumentfocus attribute. Is so, we don't want to start searching
3421 // from there but instead from the beginning of the document. Otherwise, the
3422 // content that appears before the retargetdocumentfocus element will never
3423 // get checked as it will be skipped when the focus is retargetted to it.
3424 if (forDocumentNavigation
&& nsContentUtils::IsChromeDoc(doc
)) {
3425 nsAutoString retarget
;
3427 if (rootContent
->GetAttr(kNameSpaceID_None
,
3428 nsGkAtoms::retargetdocumentfocus
, retarget
)) {
3429 nsIContent
* retargetElement
= doc
->GetElementById(retarget
);
3430 // The common case here is the urlbar where focus is on the anonymous
3431 // input inside the textbox, but the retargetdocumentfocus attribute
3432 // refers to the textbox. The Contains check will return false and the
3433 // IsInclusiveDescendantOf check will return true in this case.
3434 if (retargetElement
&&
3435 (retargetElement
== startContent
||
3436 (!retargetElement
->Contains(startContent
) &&
3437 startContent
->IsInclusiveDescendantOf(retargetElement
)))) {
3438 startContent
= rootContent
;
3443 NS_ASSERTION(startContent
, "starting content not set");
3445 // keep a reference to the starting content. If we find that again, it means
3446 // we've iterated around completely and we don't want to adjust the focus.
3447 // The skipOriginalContentCheck will be set to true only for the first time
3448 // GetNextTabbableContent is called. This ensures that we don't break out
3449 // when nothing is focused to start with. Specifically,
3450 // GetNextTabbableContent first checks the root content -- which happens to
3451 // be the same as the start content -- when nothing is focused and tabbing
3452 // forward. Without skipOriginalContentCheck set to true, we'd end up
3453 // returning right away and focusing nothing. Luckily, GetNextTabbableContent
3454 // will never wrap around on its own, and can only return the original
3455 // content when it is called a second time or later.
3456 bool skipOriginalContentCheck
= true;
3457 nsIContent
* originalStartContent
= startContent
;
3459 LOGCONTENTNAVIGATION("Focus Navigation Start Content %s", startContent
.get());
3460 LOGFOCUSNAVIGATION((" Forward: %d Tabindex: %d Ignore: %d DocNav: %d",
3461 forward
, tabIndex
, ignoreTabIndex
,
3462 forDocumentNavigation
));
3466 nsCOMPtr
<nsIContent
> nextFocus
;
3467 nsresult rv
= GetNextTabbableContent(
3468 presShell
, rootContent
,
3469 skipOriginalContentCheck
? nullptr : originalStartContent
,
3470 startContent
, forward
, tabIndex
, ignoreTabIndex
,
3471 forDocumentNavigation
, aNavigateByKey
, getter_AddRefs(nextFocus
));
3472 NS_ENSURE_SUCCESS(rv
, rv
);
3473 if (rv
== NS_SUCCESS_DOM_NO_OPERATION
) {
3474 // Navigation was redirected to a child process, so just return.
3478 // found a content node to focus.
3480 LOGCONTENTNAVIGATION("Next Content: %s", nextFocus
.get());
3482 // as long as the found node was not the same as the starting node,
3483 // set it as the return value. For document navigation, we can return
3484 // the same element in case there is only one content node that could
3485 // be returned, for example, in a child process document.
3486 if (nextFocus
!= originalStartContent
|| forDocumentNavigation
) {
3487 nextFocus
.forget(aNextContent
);
3492 if (popupFrame
&& !forDocumentNavigation
) {
3493 // in a popup, so start again from the beginning of the popup. However,
3494 // if we already started at the beginning, then there isn't anything to
3495 // focus, so just return
3496 if (startContent
!= rootContent
) {
3497 startContent
= rootContent
;
3498 tabIndex
= forward
? 1 : 0;
3505 doNavigation
= true;
3506 skipOriginalContentCheck
= forDocumentNavigation
;
3507 ignoreTabIndex
= false;
3509 if (aNoParentTraversal
) {
3510 if (startContent
== rootContent
) {
3514 startContent
= rootContent
;
3515 tabIndex
= forward
? 1 : 0;
3519 // Reached the beginning or end of the document. Next, navigate up to the
3520 // parent document and try again.
3521 nsCOMPtr
<nsPIDOMWindowOuter
> piWindow
= doc
->GetWindow();
3522 NS_ENSURE_TRUE(piWindow
, NS_ERROR_FAILURE
);
3524 nsCOMPtr
<nsIDocShell
> docShell
= piWindow
->GetDocShell();
3525 NS_ENSURE_TRUE(docShell
, NS_ERROR_FAILURE
);
3527 // Get the frame element this window is inside and, from that, get the
3528 // parent document and presshell. If there is no enclosing frame element,
3529 // then this is a top-level, embedded or remote window.
3530 startContent
= piWindow
->GetFrameElementInternal();
3532 doc
= startContent
->GetComposedDoc();
3533 NS_ENSURE_TRUE(doc
, NS_ERROR_FAILURE
);
3535 rootContent
= doc
->GetRootElement();
3536 presShell
= doc
->GetPresShell();
3538 // We can focus the root element now that we have moved to another
3540 mayFocusRoot
= true;
3542 nsIFrame
* frame
= startContent
->GetPrimaryFrame();
3547 tabIndex
= frame
->IsFocusable().mTabIndex
;
3550 ignoreTabIndex
= true;
3553 // if the frame is inside a popup, make sure to scan only within the
3554 // popup. This handles the situation of tabbing amongst elements
3555 // inside an iframe which is itself inside a popup. Otherwise,
3556 // navigation would move outside the popup when tabbing outside the
3558 if (!forDocumentNavigation
) {
3559 popupFrame
= nsLayoutUtils::GetClosestFrameOfType(
3560 frame
, LayoutFrameType::MenuPopup
);
3562 rootContent
= popupFrame
->GetContent()->AsElement();
3563 NS_ASSERTION(rootContent
, "Popup frame doesn't have a content node");
3567 if (aNavigateByKey
) {
3568 // There is no parent, so call the tree owner. This will tell the
3569 // embedder or parent process that it should take the focus.
3571 docShell
->TabToTreeOwner(forward
, forDocumentNavigation
, &tookFocus
);
3572 // If the tree owner took the focus, blur the current element.
3574 if (GetFocusedBrowsingContext() &&
3575 GetFocusedBrowsingContext()->IsInProcess()) {
3576 Blur(GetFocusedBrowsingContext(), nullptr, true, true,
3577 GenerateFocusActionId());
3579 nsCOMPtr
<nsPIDOMWindowOuter
> window
= docShell
->GetWindow();
3580 window
->SetFocusedElement(nullptr);
3586 // If we have reached the end of the top-level document, focus the
3587 // first element in the top-level document. This should always happen
3588 // when navigating by document forwards but when navigating backwards,
3589 // only do this if we started in another document or within a popup frame.
3590 // If the focus started in this window outside a popup however, we should
3591 // continue by looping around to the end again.
3592 if (forDocumentNavigation
&& (forward
|| mayFocusRoot
|| popupFrame
)) {
3593 // HTML content documents can have their root element focused (a focus
3594 // ring appears around the entire content area frame). This root
3595 // appears in the tab order before all of the elements in the document.
3596 // Chrome documents however cannot be focused directly, so instead we
3597 // focus the first focusable element within the window.
3598 // For example, the urlbar.
3599 Element
* root
= GetRootForFocus(piWindow
, doc
, true, true);
3600 return FocusFirst(root
, aNextContent
);
3603 // Once we have hit the top-level and have iterated to the end again, we
3604 // just want to break out next time we hit this spot to prevent infinite
3606 mayFocusRoot
= true;
3608 // reset the tab index and start again from the beginning or end
3609 startContent
= rootContent
;
3610 tabIndex
= forward
? 1 : 0;
3613 // wrapped all the way around and didn't find anything to move the focus
3614 // to, so just break out
3615 if (startContent
== originalStartContent
) {
3623 uint32_t nsFocusManager::FocusOptionsToFocusManagerFlags(
3624 const mozilla::dom::FocusOptions
& aOptions
) {
3626 if (aOptions
.mPreventScroll
) {
3627 flags
|= FLAG_NOSCROLL
;
3629 if (aOptions
.mPreventFocusRing
) {
3630 flags
|= FLAG_NOSHOWRING
;
3635 static bool IsHostOrSlot(const nsIContent
* aContent
) {
3636 return aContent
&& (aContent
->GetShadowRoot() ||
3637 aContent
->IsHTMLElement(nsGkAtoms::slot
));
3640 // Helper class to iterate contents in scope by traversing flattened tree
3642 class MOZ_STACK_CLASS ScopedContentTraversal
{
3644 ScopedContentTraversal(nsIContent
* aStartContent
, nsIContent
* aOwner
)
3645 : mCurrent(aStartContent
), mOwner(aOwner
) {
3646 MOZ_ASSERT(aStartContent
);
3652 void Reset() { SetCurrent(mOwner
); }
3654 nsIContent
* GetCurrent() const { return mCurrent
; }
3657 void SetCurrent(nsIContent
* aContent
) { mCurrent
= aContent
; }
3659 nsIContent
* mCurrent
;
3663 void ScopedContentTraversal::Next() {
3664 MOZ_ASSERT(mCurrent
);
3666 // Get mCurrent's first child if it's in the same scope.
3667 if (!IsHostOrSlot(mCurrent
) || mCurrent
== mOwner
) {
3668 StyleChildrenIterator
iter(mCurrent
);
3669 nsIContent
* child
= iter
.GetNextChild();
3676 // If mOwner has no children, END traversal
3677 if (mCurrent
== mOwner
) {
3678 SetCurrent(nullptr);
3682 nsIContent
* current
= mCurrent
;
3684 // Create parent's iterator and move to current
3685 nsIContent
* parent
= current
->GetFlattenedTreeParent();
3686 StyleChildrenIterator
parentIter(parent
);
3687 parentIter
.Seek(current
);
3689 // Get next sibling of current
3690 if (nsIContent
* next
= parentIter
.GetNextChild()) {
3695 // If no next sibling and parent is mOwner, END traversal
3696 if (parent
== mOwner
) {
3697 SetCurrent(nullptr);
3705 void ScopedContentTraversal::Prev() {
3706 MOZ_ASSERT(mCurrent
);
3710 if (mCurrent
== mOwner
) {
3711 // Get last child of mOwner
3712 StyleChildrenIterator
ownerIter(mOwner
, false /* aStartAtBeginning */);
3713 last
= ownerIter
.GetPreviousChild();
3717 // Create parent's iterator and move to mCurrent
3718 parent
= mCurrent
->GetFlattenedTreeParent();
3719 StyleChildrenIterator
parentIter(parent
);
3720 parentIter
.Seek(mCurrent
);
3722 // Get previous sibling
3723 last
= parentIter
.GetPreviousChild();
3728 if (IsHostOrSlot(parent
)) {
3729 // Skip contents in other scopes
3734 StyleChildrenIterator
iter(parent
, false /* aStartAtBeginning */);
3735 last
= iter
.GetPreviousChild();
3738 // If parent is mOwner and no previous sibling remains, END traversal
3739 SetCurrent(parent
== mOwner
? nullptr : parent
);
3743 * Returns scope owner of aContent.
3744 * A scope owner is either a shadow host, or slot.
3746 static nsIContent
* FindScopeOwner(nsIContent
* aContent
) {
3747 nsIContent
* currentContent
= aContent
;
3748 while (currentContent
) {
3749 nsIContent
* parent
= currentContent
->GetFlattenedTreeParent();
3751 // Shadow host / Slot
3752 if (IsHostOrSlot(parent
)) {
3756 currentContent
= parent
;
3763 * Host and Slot elements need to be handled as if they had tabindex 0 even
3764 * when they don't have the attribute. This is a helper method to get the
3765 * right value for focus navigation. If aIsFocusable is passed, it is set to
3766 * true if the element itself is focusable.
3768 static int32_t HostOrSlotTabIndexValue(const nsIContent
* aContent
,
3769 bool* aIsFocusable
= nullptr) {
3770 MOZ_ASSERT(IsHostOrSlot(aContent
));
3773 nsIFrame
* frame
= aContent
->GetPrimaryFrame();
3774 *aIsFocusable
= frame
&& frame
->IsFocusable().mTabIndex
>= 0;
3777 const nsAttrValue
* attrVal
=
3778 aContent
->AsElement()->GetParsedAttr(nsGkAtoms::tabindex
);
3783 if (attrVal
->Type() == nsAttrValue::eInteger
) {
3784 return attrVal
->GetIntegerValue();
3790 nsIContent
* nsFocusManager::GetNextTabbableContentInScope(
3791 nsIContent
* aOwner
, nsIContent
* aStartContent
,
3792 nsIContent
* aOriginalStartContent
, bool aForward
, int32_t aCurrentTabIndex
,
3793 bool aIgnoreTabIndex
, bool aForDocumentNavigation
, bool aNavigateByKey
,
3795 MOZ_ASSERT(IsHostOrSlot(aOwner
), "Scope owner should be host or slot");
3797 if (!aSkipOwner
&& (aForward
&& aOwner
== aStartContent
)) {
3798 if (nsIFrame
* frame
= aOwner
->GetPrimaryFrame()) {
3799 auto focusable
= frame
->IsFocusable();
3800 if (focusable
&& focusable
.mTabIndex
>= 0) {
3807 // Iterate contents in scope
3809 ScopedContentTraversal
contentTraversal(aStartContent
, aOwner
);
3810 nsCOMPtr
<nsIContent
> iterContent
;
3811 nsIContent
* firstNonChromeOnly
=
3812 aStartContent
->IsInNativeAnonymousSubtree()
3813 ? aStartContent
->FindFirstNonChromeOnlyAccessContent()
3816 // Iterate tab index to find corresponding contents in scope
3819 // Iterate remaining contents in scope to find next content to focus
3822 aForward
? contentTraversal
.Next() : contentTraversal
.Prev();
3823 iterContent
= contentTraversal
.GetCurrent();
3825 if (firstNonChromeOnly
&& firstNonChromeOnly
== iterContent
) {
3826 // We just broke out from the native anonymous content, so move
3827 // to the previous/next node of the native anonymous owner.
3829 contentTraversal
.Next();
3831 contentTraversal
.Prev();
3833 iterContent
= contentTraversal
.GetCurrent();
3840 int32_t tabIndex
= 0;
3841 if (iterContent
->IsInNativeAnonymousSubtree() &&
3842 iterContent
->GetPrimaryFrame()) {
3843 tabIndex
= iterContent
->GetPrimaryFrame()->IsFocusable().mTabIndex
;
3844 } else if (IsHostOrSlot(iterContent
)) {
3845 tabIndex
= HostOrSlotTabIndexValue(iterContent
);
3847 nsIFrame
* frame
= iterContent
->GetPrimaryFrame();
3851 tabIndex
= frame
->IsFocusable().mTabIndex
;
3853 if (tabIndex
< 0 || !(aIgnoreTabIndex
|| tabIndex
== aCurrentTabIndex
)) {
3857 if (!IsHostOrSlot(iterContent
)) {
3858 nsCOMPtr
<nsIContent
> elementInFrame
;
3859 bool checkSubDocument
= true;
3860 if (aForDocumentNavigation
&&
3861 TryDocumentNavigation(iterContent
, &checkSubDocument
,
3862 getter_AddRefs(elementInFrame
))) {
3863 return elementInFrame
;
3865 if (!checkSubDocument
) {
3869 if (TryToMoveFocusToSubDocument(iterContent
, aOriginalStartContent
,
3870 aForward
, aForDocumentNavigation
,
3872 getter_AddRefs(elementInFrame
))) {
3873 return elementInFrame
;
3876 // Found content to focus
3880 // Search in scope owned by iterContent
3881 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
3882 iterContent
, iterContent
, aOriginalStartContent
, aForward
,
3883 aForward
? 1 : 0, aIgnoreTabIndex
, aForDocumentNavigation
,
3884 aNavigateByKey
, false /* aSkipOwner */);
3885 if (contentToFocus
) {
3886 return contentToFocus
;
3890 // If already at lowest priority tab (0), end search completely.
3891 // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
3892 if (aCurrentTabIndex
== (aForward
? 0 : 1)) {
3896 // We've been just trying to find some focusable element, and haven't, so
3898 if (aIgnoreTabIndex
) {
3902 // Continue looking for next highest priority tabindex
3903 aCurrentTabIndex
= GetNextTabIndex(aOwner
, aCurrentTabIndex
, aForward
);
3904 contentTraversal
.Reset();
3907 // Return scope owner at last for backward navigation if its tabindex
3909 if (!aSkipOwner
&& !aForward
) {
3910 if (nsIFrame
* frame
= aOwner
->GetPrimaryFrame()) {
3911 auto focusable
= frame
->IsFocusable();
3912 if (focusable
&& focusable
.mTabIndex
>= 0) {
3921 nsIContent
* nsFocusManager::GetNextTabbableContentInAncestorScopes(
3922 nsIContent
* aStartOwner
, nsIContent
** aStartContent
,
3923 nsIContent
* aOriginalStartContent
, bool aForward
, int32_t* aCurrentTabIndex
,
3924 bool aIgnoreTabIndex
, bool aForDocumentNavigation
, bool aNavigateByKey
) {
3925 MOZ_ASSERT(aStartOwner
== FindScopeOwner(*aStartContent
),
3926 "aStartOWner should be the scope owner of aStartContent");
3927 MOZ_ASSERT(IsHostOrSlot(aStartOwner
), "scope owner should be host or slot");
3929 nsIContent
* owner
= aStartOwner
;
3930 nsIContent
* startContent
= *aStartContent
;
3931 while (IsHostOrSlot(owner
)) {
3932 int32_t tabIndex
= 0;
3933 if (IsHostOrSlot(startContent
)) {
3934 tabIndex
= HostOrSlotTabIndexValue(startContent
);
3935 } else if (nsIFrame
* frame
= startContent
->GetPrimaryFrame()) {
3936 tabIndex
= frame
->IsFocusable().mTabIndex
;
3938 startContent
->IsFocusable(&tabIndex
);
3940 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
3941 owner
, startContent
, aOriginalStartContent
, aForward
, tabIndex
,
3942 aIgnoreTabIndex
, aForDocumentNavigation
, aNavigateByKey
,
3943 false /* aSkipOwner */);
3944 if (contentToFocus
) {
3945 return contentToFocus
;
3948 startContent
= owner
;
3949 owner
= FindScopeOwner(startContent
);
3952 // If not found in shadow DOM, search from the top level shadow host in light
3954 *aStartContent
= startContent
;
3955 *aCurrentTabIndex
= HostOrSlotTabIndexValue(startContent
);
3960 static nsIContent
* GetTopLevelScopeOwner(nsIContent
* aContent
) {
3961 nsIContent
* topLevelScopeOwner
= nullptr;
3963 if (HTMLSlotElement
* slot
= aContent
->GetAssignedSlot()) {
3965 topLevelScopeOwner
= aContent
;
3966 } else if (ShadowRoot
* shadowRoot
= aContent
->GetContainingShadow()) {
3967 aContent
= shadowRoot
->Host();
3968 topLevelScopeOwner
= aContent
;
3970 aContent
= aContent
->GetParent();
3971 if (aContent
&& HTMLSlotElement::FromNode(aContent
)) {
3972 topLevelScopeOwner
= aContent
;
3977 return topLevelScopeOwner
;
3980 nsresult
nsFocusManager::GetNextTabbableContent(
3981 PresShell
* aPresShell
, nsIContent
* aRootContent
,
3982 nsIContent
* aOriginalStartContent
, nsIContent
* aStartContent
, bool aForward
,
3983 int32_t aCurrentTabIndex
, bool aIgnoreTabIndex
, bool aForDocumentNavigation
,
3984 bool aNavigateByKey
, nsIContent
** aResultContent
) {
3985 *aResultContent
= nullptr;
3987 if (!aStartContent
) {
3991 nsIContent
* startContent
= aStartContent
;
3992 nsIContent
* currentTopLevelScopeOwner
= GetTopLevelScopeOwner(startContent
);
3994 LOGCONTENTNAVIGATION("GetNextTabbable: %s", startContent
);
3995 LOGFOCUSNAVIGATION((" tabindex: %d", aCurrentTabIndex
));
3997 // If startContent is a shadow host or slot in forward navigation,
3998 // search in scope owned by startContent
3999 if (aForward
&& IsHostOrSlot(startContent
)) {
4000 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
4001 startContent
, startContent
, aOriginalStartContent
, aForward
,
4002 aForward
? 1 : 0, aIgnoreTabIndex
, aForDocumentNavigation
,
4003 aNavigateByKey
, true /* aSkipOwner */);
4004 if (contentToFocus
) {
4005 NS_ADDREF(*aResultContent
= contentToFocus
);
4010 // If startContent is in a scope owned by Shadow DOM search from scope
4011 // including startContent
4012 if (nsIContent
* owner
= FindScopeOwner(startContent
)) {
4013 nsIContent
* contentToFocus
= GetNextTabbableContentInAncestorScopes(
4014 owner
, &startContent
, aOriginalStartContent
, aForward
,
4015 &aCurrentTabIndex
, aIgnoreTabIndex
, aForDocumentNavigation
,
4017 if (contentToFocus
) {
4018 NS_ADDREF(*aResultContent
= contentToFocus
);
4023 // If we reach here, it means no next tabbable content in shadow DOM.
4024 // We need to continue searching in light DOM, starting at the top level
4025 // shadow host in light DOM (updated startContent) and its tabindex
4026 // (updated aCurrentTabIndex).
4027 MOZ_ASSERT(!FindScopeOwner(startContent
),
4028 "startContent should not be owned by Shadow DOM at this point");
4030 nsPresContext
* presContext
= aPresShell
->GetPresContext();
4032 bool getNextFrame
= true;
4033 nsCOMPtr
<nsIContent
> iterStartContent
= startContent
;
4034 nsIContent
* topLevelScopeStartContent
= startContent
;
4035 // Iterate tab index to find corresponding contents
4037 nsIFrame
* frame
= iterStartContent
->GetPrimaryFrame();
4038 // if there is no frame, look for another content node that has a frame
4040 // if the root content doesn't have a frame, just return
4041 if (iterStartContent
== aRootContent
) {
4045 // look for the next or previous content node in tree order
4046 iterStartContent
= aForward
? iterStartContent
->GetNextNode()
4047 : iterStartContent
->GetPreviousContent();
4048 if (!iterStartContent
) {
4052 frame
= iterStartContent
->GetPrimaryFrame();
4053 // Host without frame, enter its scope.
4054 if (!frame
&& iterStartContent
->GetShadowRoot()) {
4055 int32_t tabIndex
= HostOrSlotTabIndexValue(iterStartContent
);
4056 if (tabIndex
>= 0 &&
4057 (aIgnoreTabIndex
|| aCurrentTabIndex
== tabIndex
)) {
4058 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
4059 iterStartContent
, iterStartContent
, aOriginalStartContent
,
4060 aForward
, aForward
? 1 : 0, aIgnoreTabIndex
,
4061 aForDocumentNavigation
, aNavigateByKey
, true /* aSkipOwner */);
4062 if (contentToFocus
) {
4063 NS_ADDREF(*aResultContent
= contentToFocus
);
4068 // we've already skipped over the initial focused content, so we
4069 // don't want to traverse frames.
4070 getNextFrame
= false;
4073 nsCOMPtr
<nsIFrameEnumerator
> frameTraversal
;
4075 // For tab navigation, pass false for aSkipPopupChecks so that we don't
4076 // iterate into or out of a popup. For document naviation pass true to
4077 // ignore these boundaries.
4078 nsresult rv
= NS_NewFrameTraversal(
4079 getter_AddRefs(frameTraversal
), presContext
, frame
, ePreOrder
,
4081 false, // aLockInScrollView
4082 true, // aFollowOOFs
4083 aForDocumentNavigation
// aSkipPopupChecks
4085 NS_ENSURE_SUCCESS(rv
, rv
);
4087 if (iterStartContent
== aRootContent
) {
4089 frameTraversal
->Last();
4090 } else if (aRootContent
->IsFocusable()) {
4091 frameTraversal
->Next();
4093 frame
= frameTraversal
->CurrentItem();
4094 } else if (getNextFrame
&&
4095 (!iterStartContent
||
4096 !iterStartContent
->IsHTMLElement(nsGkAtoms::area
))) {
4097 // Need to do special check in case we're in an imagemap which has
4098 // multiple content nodes per frame, so don't skip over the starting
4100 frame
= frameTraversal
->Traverse(aForward
);
4104 nsIContent
* oldTopLevelScopeOwner
= nullptr;
4105 // Walk frames to find something tabbable matching aCurrentTabIndex
4107 // Try to find the topmost scope owner, since we want to skip the node
4108 // that is not owned by document in frame traversal.
4109 nsIContent
* currentContent
= frame
->GetContent();
4110 if (currentTopLevelScopeOwner
) {
4111 oldTopLevelScopeOwner
= currentTopLevelScopeOwner
;
4113 currentTopLevelScopeOwner
= GetTopLevelScopeOwner(currentContent
);
4114 if (currentTopLevelScopeOwner
&&
4115 currentTopLevelScopeOwner
== oldTopLevelScopeOwner
) {
4116 // We're within non-document scope, continue.
4119 frameTraversal
->Next();
4121 frameTraversal
->Prev();
4123 frame
= static_cast<nsIFrame
*>(frameTraversal
->CurrentItem());
4124 // For the usage of GetPrevContinuation, see the comment
4125 // at the end of while (frame) loop.
4126 } while (frame
&& frame
->GetPrevContinuation());
4130 // For document navigation, check if this element is an open panel. Since
4131 // panels aren't focusable (tabIndex would be -1), we'll just assume that
4132 // for document navigation, the tabIndex is 0.
4133 if (aForDocumentNavigation
&& currentContent
&& (aCurrentTabIndex
== 0) &&
4134 currentContent
->IsXULElement(nsGkAtoms::panel
)) {
4135 nsMenuPopupFrame
* popupFrame
= do_QueryFrame(frame
);
4136 // Check if the panel is open. Closed panels are ignored since you can't
4137 // focus anything in them.
4138 if (popupFrame
&& popupFrame
->IsOpen()) {
4139 // When moving backward, skip the popup we started in otherwise it
4140 // will be selected again.
4141 bool validPopup
= true;
4143 nsIContent
* content
= topLevelScopeStartContent
;
4145 if (content
== currentContent
) {
4150 content
= content
->GetParent();
4155 // Since a panel isn't focusable itself, find the first focusable
4156 // content within the popup. If there isn't any focusable content
4157 // in the popup, skip this popup and continue iterating through the
4158 // frames. We pass the panel itself (currentContent) as the starting
4159 // and root content, so that we only find content within the panel.
4160 // Note also that we pass false for aForDocumentNavigation since we
4161 // want to locate the first content, not the first document.
4162 nsresult rv
= GetNextTabbableContent(
4163 aPresShell
, currentContent
, nullptr, currentContent
, true, 1,
4164 false, false, aNavigateByKey
, aResultContent
);
4165 if (NS_SUCCEEDED(rv
) && *aResultContent
) {
4172 // As of now, 2018/04/12, sequential focus navigation is still
4173 // in the obsolete Shadow DOM specification.
4174 // http://w3c.github.io/webcomponents/spec/shadow/#sequential-focus-navigation
4175 // "if ELEMENT is focusable, a shadow host, or a slot element,
4176 // append ELEMENT to NAVIGATION-ORDER."
4177 // and later in "For each element ELEMENT in NAVIGATION-ORDER: "
4178 // hosts and slots are handled before other elements.
4179 if (currentTopLevelScopeOwner
) {
4180 bool focusableHostSlot
;
4181 int32_t tabIndex
= HostOrSlotTabIndexValue(currentTopLevelScopeOwner
,
4182 &focusableHostSlot
);
4183 // Host or slot itself isn't focusable or going backwards, enter its
4185 if ((!aForward
|| !focusableHostSlot
) && tabIndex
>= 0 &&
4186 (aIgnoreTabIndex
|| aCurrentTabIndex
== tabIndex
)) {
4187 nsIContent
* contentToFocus
= GetNextTabbableContentInScope(
4188 currentTopLevelScopeOwner
, currentTopLevelScopeOwner
,
4189 aOriginalStartContent
, aForward
, aForward
? 1 : 0,
4190 aIgnoreTabIndex
, aForDocumentNavigation
, aNavigateByKey
,
4191 true /* aSkipOwner */);
4192 if (contentToFocus
) {
4193 NS_ADDREF(*aResultContent
= contentToFocus
);
4196 // If we've wrapped around already, then carry on.
4197 if (aOriginalStartContent
&&
4198 currentTopLevelScopeOwner
==
4199 GetTopLevelScopeOwner(aOriginalStartContent
)) {
4200 // FIXME: Shouldn't this return null instead? aOriginalStartContent
4201 // isn't focusable after all.
4202 NS_ADDREF(*aResultContent
= aOriginalStartContent
);
4206 // There is no next tabbable content in currentTopLevelScopeOwner's
4207 // scope. We should continue the loop in order to skip all contents that
4208 // is in currentTopLevelScopeOwner's scope.
4212 MOZ_ASSERT(!GetTopLevelScopeOwner(currentContent
),
4213 "currentContent should be in top-level-scope at this point");
4215 // TabIndex not set defaults to 0 for form elements, anchors and other
4216 // elements that are normally focusable. Tabindex defaults to -1
4217 // for elements that are not normally focusable.
4218 // The returned computed tabindex from IsFocusable() is as follows:
4220 // < 0 not tabbable at all
4221 // == 0 in normal tab order (last after positive tabindexed items)
4222 // > 0 can be tabbed to in the order specified by this value
4224 int32_t tabIndex
= frame
->IsFocusable().mTabIndex
;
4226 LOGCONTENTNAVIGATION("Next Tabbable %s:", frame
->GetContent());
4228 (" with tabindex: %d expected: %d", tabIndex
, aCurrentTabIndex
));
4230 if (tabIndex
>= 0) {
4231 NS_ASSERTION(currentContent
,
4232 "IsFocusable set a tabindex for a frame with no content");
4233 if (!aForDocumentNavigation
&&
4234 currentContent
->IsHTMLElement(nsGkAtoms::img
) &&
4235 currentContent
->AsElement()->HasAttr(kNameSpaceID_None
,
4236 nsGkAtoms::usemap
)) {
4237 // This is an image with a map. Image map areas are not traversed by
4238 // nsIFrameTraversal so look for the next or previous area element.
4239 nsIContent
* areaContent
= GetNextTabbableMapArea(
4240 aForward
, aCurrentTabIndex
, currentContent
->AsElement(),
4243 NS_ADDREF(*aResultContent
= areaContent
);
4246 } else if (aIgnoreTabIndex
|| aCurrentTabIndex
== tabIndex
) {
4247 // break out if we've wrapped around to the start again.
4248 if (aOriginalStartContent
&&
4249 currentContent
== aOriginalStartContent
) {
4250 NS_ADDREF(*aResultContent
= currentContent
);
4254 // If this is a remote child browser, call NavigateDocument to have
4255 // the child process continue the navigation. Return a special error
4256 // code to have the caller return early. If the child ends up not
4257 // being focusable in some way, the child process will call back
4258 // into document navigation again by calling MoveFocus.
4259 if (BrowserParent
* remote
= BrowserParent::GetFrom(currentContent
)) {
4260 if (aNavigateByKey
) {
4261 remote
->NavigateByKey(aForward
, aForDocumentNavigation
);
4262 return NS_SUCCESS_DOM_NO_OPERATION
;
4267 // Same as above but for out-of-process iframes
4268 if (auto* bbc
= BrowserBridgeChild::GetFrom(currentContent
)) {
4269 if (aNavigateByKey
) {
4270 bbc
->NavigateByKey(aForward
, aForDocumentNavigation
);
4271 return NS_SUCCESS_DOM_NO_OPERATION
;
4276 // Next, for document navigation, check if this a non-remote child
4278 bool checkSubDocument
= true;
4279 if (aForDocumentNavigation
&&
4280 TryDocumentNavigation(currentContent
, &checkSubDocument
,
4285 if (checkSubDocument
) {
4286 // found a node with a matching tab index. Check if it is a child
4287 // frame. If so, navigate into the child frame instead.
4288 if (TryToMoveFocusToSubDocument(
4289 currentContent
, aOriginalStartContent
, aForward
,
4290 aForDocumentNavigation
, aNavigateByKey
, aResultContent
)) {
4291 MOZ_ASSERT(*aResultContent
);
4294 // otherwise, use this as the next content node to tab to, unless
4295 // this was the element we started on. This would happen for
4296 // instance on an element with child frames, where frame navigation
4297 // could return the original element again. In that case, just skip
4298 // it. Also, if the next content node is the root content, then
4299 // return it. This latter case would happen only if someone made a
4301 // Also, when going backwards, check to ensure that the focus
4302 // wouldn't be redirected. Otherwise, for example, when an input in
4303 // a textbox is focused, the enclosing textbox would be found and
4304 // the same inner input would be returned again.
4305 else if (currentContent
== aRootContent
||
4306 (currentContent
!= startContent
&&
4307 (aForward
|| !GetRedirectedFocus(currentContent
)))) {
4308 NS_ADDREF(*aResultContent
= currentContent
);
4313 } else if (aOriginalStartContent
&&
4314 currentContent
== aOriginalStartContent
) {
4315 // not focusable, so return if we have wrapped around to the original
4316 // content. This is necessary in case the original starting content was
4319 // FIXME: Shouldn't this return null instead? currentContent isn't
4320 // focusable after all.
4321 NS_ADDREF(*aResultContent
= currentContent
);
4325 // Move to the next or previous frame, but ignore continuation frames
4326 // since only the first frame should be involved in focusability.
4327 // Otherwise, a loop will occur in the following example:
4328 // <span tabindex="1">...<a/><a/>...</span>
4329 // where the text wraps onto multiple lines. Tabbing from the second
4330 // link can find one of the span's continuation frames between the link
4331 // and the end of the span, and the span would end up getting focused
4335 frameTraversal
->Next();
4337 frameTraversal
->Prev();
4339 frame
= static_cast<nsIFrame
*>(frameTraversal
->CurrentItem());
4340 } while (frame
&& frame
->GetPrevContinuation());
4343 // If already at lowest priority tab (0), end search completely.
4344 // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
4345 if (aCurrentTabIndex
== (aForward
? 0 : 1)) {
4346 // if going backwards, the canvas should be focused once the beginning
4347 // has been reached, so get the root element.
4349 nsCOMPtr
<nsPIDOMWindowOuter
> window
= GetCurrentWindow(aRootContent
);
4350 NS_ENSURE_TRUE(window
, NS_ERROR_FAILURE
);
4352 RefPtr
<Element
> docRoot
= GetRootForFocus(
4353 window
, aRootContent
->GetComposedDoc(), false, true);
4354 FocusFirst(docRoot
, aResultContent
);
4359 // continue looking for next highest priority tabindex
4361 GetNextTabIndex(aRootContent
, aCurrentTabIndex
, aForward
);
4362 startContent
= iterStartContent
= aRootContent
;
4363 currentTopLevelScopeOwner
= GetTopLevelScopeOwner(startContent
);
4369 bool nsFocusManager::TryDocumentNavigation(nsIContent
* aCurrentContent
,
4370 bool* aCheckSubDocument
,
4371 nsIContent
** aResultContent
) {
4372 *aCheckSubDocument
= true;
4373 if (Element
* docRoot
= GetRootForChildDocument(aCurrentContent
)) {
4374 // If GetRootForChildDocument returned something then call
4375 // FocusFirst to find the root or first element to focus within
4376 // the child document. If this is a frameset though, skip this and
4377 // fall through to normal tab navigation to iterate into
4378 // the frameset's frames and locate the first focusable frame.
4379 if (!docRoot
->IsHTMLElement(nsGkAtoms::frameset
)) {
4380 *aCheckSubDocument
= false;
4381 Unused
<< FocusFirst(docRoot
, aResultContent
);
4382 return *aResultContent
!= nullptr;
4385 // Set aCheckSubDocument to false, as this was neither a frame
4386 // type element or a child document that was focusable.
4387 *aCheckSubDocument
= false;
4393 bool nsFocusManager::TryToMoveFocusToSubDocument(
4394 nsIContent
* aCurrentContent
, nsIContent
* aOriginalStartContent
,
4395 bool aForward
, bool aForDocumentNavigation
, bool aNavigateByKey
,
4396 nsIContent
** aResultContent
) {
4397 Document
* doc
= aCurrentContent
->GetComposedDoc();
4398 NS_ASSERTION(doc
, "content not in document");
4399 Document
* subdoc
= doc
->GetSubDocumentFor(aCurrentContent
);
4400 if (subdoc
&& !subdoc
->EventHandlingSuppressed()) {
4402 // When tabbing forward into a frame, return the root
4403 // frame so that the canvas becomes focused.
4404 if (nsCOMPtr
<nsPIDOMWindowOuter
> subframe
= subdoc
->GetWindow()) {
4405 *aResultContent
= GetRootForFocus(subframe
, subdoc
, false, true);
4406 if (*aResultContent
) {
4407 NS_ADDREF(*aResultContent
);
4412 Element
* rootElement
= subdoc
->GetRootElement();
4413 PresShell
* subPresShell
= subdoc
->GetPresShell();
4414 if (rootElement
&& subPresShell
) {
4415 nsresult rv
= GetNextTabbableContent(
4416 subPresShell
, rootElement
, aOriginalStartContent
, rootElement
,
4417 aForward
, (aForward
? 1 : 0), false, aForDocumentNavigation
,
4418 aNavigateByKey
, aResultContent
);
4419 NS_ENSURE_SUCCESS(rv
, false);
4420 if (*aResultContent
) {
4428 nsIContent
* nsFocusManager::GetNextTabbableMapArea(bool aForward
,
4429 int32_t aCurrentTabIndex
,
4430 Element
* aImageContent
,
4431 nsIContent
* aStartContent
) {
4432 if (aImageContent
->IsInComposedDoc()) {
4433 HTMLImageElement
* imgElement
= HTMLImageElement::FromNode(aImageContent
);
4434 // The caller should check the element type, so we can assert here.
4435 MOZ_ASSERT(imgElement
);
4437 nsCOMPtr
<nsIContent
> mapContent
= imgElement
->FindImageMap();
4441 uint32_t count
= mapContent
->GetChildCount();
4442 // First see if the the start content is in this map
4444 int32_t index
= mapContent
->ComputeIndexOf(aStartContent
);
4446 if (index
< 0 || (aStartContent
->IsFocusable(&tabIndex
) &&
4447 tabIndex
!= aCurrentTabIndex
)) {
4448 // If aStartContent is in this map we must start iterating past it.
4449 // We skip the case where aStartContent has tabindex == aStartContent
4450 // since the next tab ordered element might be before it
4451 // (or after for backwards) in the child list.
4452 index
= aForward
? -1 : (int32_t)count
;
4455 // GetChildAt_Deprecated will return nullptr if our index < 0 or index >=
4457 nsCOMPtr
<nsIContent
> areaContent
;
4458 while ((areaContent
= mapContent
->GetChildAt_Deprecated(
4459 aForward
? ++index
: --index
)) != nullptr) {
4460 if (areaContent
->IsFocusable(&tabIndex
) && tabIndex
== aCurrentTabIndex
) {
4469 int32_t nsFocusManager::GetNextTabIndex(nsIContent
* aParent
,
4470 int32_t aCurrentTabIndex
,
4472 int32_t tabIndex
, childTabIndex
;
4473 StyleChildrenIterator
iter(aParent
);
4477 for (nsIContent
* child
= iter
.GetNextChild(); child
;
4478 child
= iter
.GetNextChild()) {
4479 // Skip child's descendants if child is a shadow host or slot, as they are
4480 // in the focus navigation scope owned by child's shadow root
4481 if (!IsHostOrSlot(child
)) {
4482 childTabIndex
= GetNextTabIndex(child
, aCurrentTabIndex
, aForward
);
4483 if (childTabIndex
> aCurrentTabIndex
&& childTabIndex
!= tabIndex
) {
4484 tabIndex
= (tabIndex
== 0 || childTabIndex
< tabIndex
) ? childTabIndex
4489 nsAutoString tabIndexStr
;
4490 if (child
->IsElement()) {
4491 child
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::tabindex
,
4495 int32_t val
= tabIndexStr
.ToInteger(&ec
);
4496 if (NS_SUCCEEDED(ec
) && val
> aCurrentTabIndex
&& val
!= tabIndex
) {
4497 tabIndex
= (tabIndex
== 0 || val
< tabIndex
) ? val
: tabIndex
;
4500 } else { /* !aForward */
4502 for (nsIContent
* child
= iter
.GetNextChild(); child
;
4503 child
= iter
.GetNextChild()) {
4504 // Skip child's descendants if child is a shadow host or slot, as they are
4505 // in the focus navigation scope owned by child's shadow root
4506 if (!IsHostOrSlot(child
)) {
4507 childTabIndex
= GetNextTabIndex(child
, aCurrentTabIndex
, aForward
);
4508 if ((aCurrentTabIndex
== 0 && childTabIndex
> tabIndex
) ||
4509 (childTabIndex
< aCurrentTabIndex
&& childTabIndex
> tabIndex
)) {
4510 tabIndex
= childTabIndex
;
4514 nsAutoString tabIndexStr
;
4515 if (child
->IsElement()) {
4516 child
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::tabindex
,
4520 int32_t val
= tabIndexStr
.ToInteger(&ec
);
4521 if (NS_SUCCEEDED(ec
)) {
4522 if ((aCurrentTabIndex
== 0 && val
> tabIndex
) ||
4523 (val
< aCurrentTabIndex
&& val
> tabIndex
)) {
4533 nsresult
nsFocusManager::FocusFirst(Element
* aRootElement
,
4534 nsIContent
** aNextContent
) {
4535 if (!aRootElement
) {
4539 Document
* doc
= aRootElement
->GetComposedDoc();
4541 if (nsContentUtils::IsChromeDoc(doc
)) {
4542 // If the redirectdocumentfocus attribute is set, redirect the focus to a
4543 // specific element. This is primarily used to retarget the focus to the
4544 // urlbar during document navigation.
4545 nsAutoString retarget
;
4547 if (aRootElement
->GetAttr(kNameSpaceID_None
,
4548 nsGkAtoms::retargetdocumentfocus
, retarget
)) {
4549 nsCOMPtr
<Element
> element
= doc
->GetElementById(retarget
);
4550 nsCOMPtr
<nsIContent
> retargetElement
=
4551 FlushAndCheckIfFocusable(element
, 0);
4552 if (retargetElement
) {
4553 retargetElement
.forget(aNextContent
);
4559 nsCOMPtr
<nsIDocShell
> docShell
= doc
->GetDocShell();
4560 if (docShell
->ItemType() == nsIDocShellTreeItem::typeChrome
) {
4561 // If the found content is in a chrome shell, navigate forward one
4562 // tabbable item so that the first item is focused. Note that we
4563 // always go forward and not back here.
4564 PresShell
* presShell
= doc
->GetPresShell();
4566 return GetNextTabbableContent(presShell
, aRootElement
, nullptr,
4567 aRootElement
, true, 1, false, false, true,
4573 NS_ADDREF(*aNextContent
= aRootElement
);
4577 Element
* nsFocusManager::GetRootForFocus(nsPIDOMWindowOuter
* aWindow
,
4578 Document
* aDocument
,
4579 bool aForDocumentNavigation
,
4580 bool aCheckVisibility
) {
4581 if (!aForDocumentNavigation
) {
4582 nsCOMPtr
<nsIDocShell
> docShell
= aWindow
->GetDocShell();
4583 if (docShell
->ItemType() == nsIDocShellTreeItem::typeChrome
) {
4588 if (aCheckVisibility
&& !IsWindowVisible(aWindow
)) return nullptr;
4590 // If the body is contenteditable, use the editor's root element rather than
4591 // the actual root element.
4592 RefPtr
<Element
> rootElement
=
4593 nsLayoutUtils::GetEditableRootContentByContentEditable(aDocument
);
4594 if (!rootElement
|| !rootElement
->GetPrimaryFrame()) {
4595 rootElement
= aDocument
->GetRootElement();
4601 if (aCheckVisibility
&& !rootElement
->GetPrimaryFrame()) {
4605 // Finally, check if this is a frameset
4606 if (aDocument
&& aDocument
->IsHTMLOrXHTML()) {
4607 Element
* htmlChild
= aDocument
->GetHtmlChildElement(nsGkAtoms::frameset
);
4609 // In document navigation mode, return the frameset so that navigation
4610 // descends into the child frames.
4611 return aForDocumentNavigation
? htmlChild
: nullptr;
4618 Element
* nsFocusManager::GetRootForChildDocument(nsIContent
* aContent
) {
4619 // Check for elements that represent child documents, that is, browsers,
4620 // editors or frames from a frameset. We don't include iframes since we
4621 // consider them to be an integral part of the same window or page.
4622 if (!aContent
|| !(aContent
->IsXULElement(nsGkAtoms::browser
) ||
4623 aContent
->IsXULElement(nsGkAtoms::editor
) ||
4624 aContent
->IsHTMLElement(nsGkAtoms::frame
))) {
4628 Document
* doc
= aContent
->GetComposedDoc();
4633 Document
* subdoc
= doc
->GetSubDocumentFor(aContent
);
4634 if (!subdoc
|| subdoc
->EventHandlingSuppressed()) {
4638 nsCOMPtr
<nsPIDOMWindowOuter
> window
= subdoc
->GetWindow();
4639 return GetRootForFocus(window
, subdoc
, true, true);
4642 void nsFocusManager::GetFocusInSelection(nsPIDOMWindowOuter
* aWindow
,
4643 nsIContent
* aStartSelection
,
4644 nsIContent
* aEndSelection
,
4645 nsIContent
** aFocusedContent
) {
4646 *aFocusedContent
= nullptr;
4648 nsCOMPtr
<nsIContent
> testContent
= aStartSelection
;
4649 nsCOMPtr
<nsIContent
> nextTestContent
= aEndSelection
;
4651 nsCOMPtr
<nsIContent
> currentFocus
= aWindow
->GetFocusedElement();
4653 // We now have the correct start node in selectionContent!
4654 // Search for focusable elements, starting with selectionContent
4656 // Method #1: Keep going up while we look - an ancestor might be focusable
4657 // We could end the loop earlier, such as when we're no longer
4658 // in the same frame, by comparing selectionContent->GetPrimaryFrame()
4659 // with a variable holding the starting selectionContent
4660 while (testContent
) {
4661 // Keep testing while selectionContent is equal to something,
4662 // eventually we'll run out of ancestors
4664 nsCOMPtr
<nsIURI
> uri
;
4665 if (testContent
== currentFocus
||
4666 testContent
->IsLink(getter_AddRefs(uri
))) {
4667 testContent
.forget(aFocusedContent
);
4672 testContent
= testContent
->GetParent();
4675 // We run this loop again, checking the ancestor chain of the selection's
4677 testContent
= nextTestContent
;
4678 nextTestContent
= nullptr;
4682 // We couldn't find an anchor that was an ancestor of the selection start
4683 // Method #2: look for anchor in selection's primary range (depth first
4686 nsCOMPtr
<nsIContent
> selectionNode
= aStartSelection
;
4687 nsCOMPtr
<nsIContent
> endSelectionNode
= aEndSelection
;
4688 nsCOMPtr
<nsIContent
> testNode
;
4691 testContent
= selectionNode
;
4693 // We're looking for any focusable link that could be part of the
4694 // main document's selection.
4695 nsCOMPtr
<nsIURI
> uri
;
4696 if (testContent
== currentFocus
||
4697 testContent
->IsLink(getter_AddRefs(uri
))) {
4698 testContent
.forget(aFocusedContent
);
4702 nsIContent
* testNode
= selectionNode
->GetFirstChild();
4704 selectionNode
= testNode
;
4708 if (selectionNode
== endSelectionNode
) {
4711 testNode
= selectionNode
->GetNextSibling();
4713 selectionNode
= testNode
;
4718 // GetParent is OK here, instead of GetParentNode, because the only case
4719 // where the latter returns something different from the former is when
4720 // GetParentNode is the document. But in that case we would simply get
4721 // null for selectionNode when setting it to testNode->GetNextSibling()
4722 // (because a document has no next sibling). And then the next iteration
4723 // of this loop would get null for GetParentNode anyway, and break out of
4725 testNode
= selectionNode
->GetParent();
4726 if (!testNode
|| testNode
== endSelectionNode
) {
4727 selectionNode
= nullptr;
4730 selectionNode
= testNode
->GetNextSibling();
4731 if (selectionNode
) {
4734 selectionNode
= testNode
;
4736 } while (selectionNode
&& selectionNode
!= endSelectionNode
);
4739 static void MaybeUnlockPointer(BrowsingContext
* aCurrentFocusedContext
) {
4740 if (!PointerLockManager::IsInLockContext(aCurrentFocusedContext
)) {
4741 PointerLockManager::Unlock();
4745 class PointerUnlocker
: public Runnable
{
4747 PointerUnlocker() : mozilla::Runnable("PointerUnlocker") {
4748 MOZ_ASSERT(XRE_IsParentProcess());
4749 MOZ_ASSERT(!PointerUnlocker::sActiveUnlocker
);
4750 PointerUnlocker::sActiveUnlocker
= this;
4753 ~PointerUnlocker() {
4754 if (PointerUnlocker::sActiveUnlocker
== this) {
4755 PointerUnlocker::sActiveUnlocker
= nullptr;
4759 NS_IMETHOD
Run() override
{
4760 if (PointerUnlocker::sActiveUnlocker
== this) {
4761 PointerUnlocker::sActiveUnlocker
= nullptr;
4763 NS_ENSURE_STATE(nsFocusManager::GetFocusManager());
4764 nsPIDOMWindowOuter
* focused
=
4765 nsFocusManager::GetFocusManager()->GetFocusedWindow();
4766 MaybeUnlockPointer(focused
? focused
->GetBrowsingContext() : nullptr);
4770 static PointerUnlocker
* sActiveUnlocker
;
4773 PointerUnlocker
* PointerUnlocker::sActiveUnlocker
= nullptr;
4775 void nsFocusManager::SetFocusedBrowsingContext(BrowsingContext
* aContext
,
4776 uint64_t aActionId
) {
4777 if (XRE_IsParentProcess()) {
4780 MOZ_ASSERT(!ActionIdComparableAndLower(
4781 aActionId
, mActionIdForFocusedBrowsingContextInContent
));
4782 mFocusedBrowsingContextInContent
= aContext
;
4783 mActionIdForFocusedBrowsingContextInContent
= aActionId
;
4785 // We don't send the unset but instead expect the set from
4786 // elsewhere to take care of it. XXX Is that bad?
4787 MOZ_ASSERT(aContext
->IsInProcess());
4788 mozilla::dom::ContentChild
* contentChild
=
4789 mozilla::dom::ContentChild::GetSingleton();
4790 MOZ_ASSERT(contentChild
);
4791 contentChild
->SendSetFocusedBrowsingContext(aContext
, aActionId
);
4795 void nsFocusManager::SetFocusedBrowsingContextFromOtherProcess(
4796 BrowsingContext
* aContext
, uint64_t aActionId
) {
4797 MOZ_ASSERT(!XRE_IsParentProcess());
4798 MOZ_ASSERT(aContext
);
4799 if (ActionIdComparableAndLower(aActionId
,
4800 mActionIdForFocusedBrowsingContextInContent
)) {
4801 // Unclear if this ever happens.
4803 ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
4804 "focused from another process due to stale action id.",
4808 if (aContext
->IsInProcess()) {
4809 // This message has been in transit for long enough that
4810 // the process association of aContext has changed since
4811 // the other content process sent the message, because
4812 // an iframe in that process became an out-of-process
4813 // iframe while the IPC broadcast that we're receiving
4814 // was in-flight. Let's just ignore this.
4816 ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
4817 "focused from another process.",
4821 mFocusedBrowsingContextInContent
= aContext
;
4822 mActionIdForFocusedBrowsingContextInContent
= aActionId
;
4823 mFocusedElement
= nullptr;
4826 bool nsFocusManager::SetFocusedBrowsingContextInChrome(
4827 mozilla::dom::BrowsingContext
* aContext
, uint64_t aActionId
) {
4828 MOZ_ASSERT(aActionId
);
4829 if (ProcessPendingFocusedBrowsingContextActionId(aActionId
)) {
4830 MOZ_DIAGNOSTIC_ASSERT(!ActionIdComparableAndLower(
4831 aActionId
, mActionIdForFocusedBrowsingContextInChrome
));
4832 mFocusedBrowsingContextInChrome
= aContext
;
4833 mActionIdForFocusedBrowsingContextInChrome
= aActionId
;
4839 BrowsingContext
* nsFocusManager::GetFocusedBrowsingContextInChrome() {
4840 return mFocusedBrowsingContextInChrome
;
4843 void nsFocusManager::BrowsingContextDetached(BrowsingContext
* aContext
) {
4844 if (mFocusedBrowsingContextInChrome
== aContext
) {
4845 mFocusedBrowsingContextInChrome
= nullptr;
4846 // Deliberately not adjusting the corresponding action id, because
4847 // we don't want changes from the past to take effect.
4849 if (mActiveBrowsingContextInChrome
== aContext
) {
4850 mActiveBrowsingContextInChrome
= nullptr;
4851 // Deliberately not adjusting the corresponding action id, because
4852 // we don't want changes from the past to take effect.
4856 void nsFocusManager::SetActiveBrowsingContextInContent(
4857 mozilla::dom::BrowsingContext
* aContext
, uint64_t aActionId
) {
4858 MOZ_ASSERT(!XRE_IsParentProcess());
4859 MOZ_ASSERT(!aContext
|| aContext
->IsInProcess());
4860 mozilla::dom::ContentChild
* contentChild
=
4861 mozilla::dom::ContentChild::GetSingleton();
4862 MOZ_ASSERT(contentChild
);
4864 if (ActionIdComparableAndLower(aActionId
,
4865 mActionIdForActiveBrowsingContextInContent
)) {
4867 ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
4868 "the active browsing context due to a stale action id.",
4873 if (aContext
!= mActiveBrowsingContextInContent
) {
4875 contentChild
->SendSetActiveBrowsingContext(aContext
, aActionId
);
4876 } else if (mActiveBrowsingContextInContent
) {
4877 // We want to sync this over only if this isn't happening
4878 // due to the active BrowsingContext switching processes,
4879 // in which case the BrowserChild has already marked itself
4881 nsPIDOMWindowOuter
* outer
=
4882 mActiveBrowsingContextInContent
->GetDOMWindow();
4884 nsPIDOMWindowInner
* inner
= outer
->GetCurrentInnerWindow();
4886 WindowGlobalChild
* globalChild
= inner
->GetWindowGlobalChild();
4888 RefPtr
<BrowserChild
> browserChild
= globalChild
->GetBrowserChild();
4889 if (browserChild
&& !browserChild
->IsDestroyed()) {
4890 contentChild
->SendUnsetActiveBrowsingContext(
4891 mActiveBrowsingContextInContent
, aActionId
);
4898 mActiveBrowsingContextInContentSetFromOtherProcess
= false;
4899 mActiveBrowsingContextInContent
= aContext
;
4900 mActionIdForActiveBrowsingContextInContent
= aActionId
;
4901 MaybeUnlockPointer(aContext
);
4904 void nsFocusManager::SetActiveBrowsingContextFromOtherProcess(
4905 BrowsingContext
* aContext
, uint64_t aActionId
) {
4906 MOZ_ASSERT(!XRE_IsParentProcess());
4907 MOZ_ASSERT(aContext
);
4908 if (ActionIdComparableAndLower(aActionId
,
4909 mActionIdForActiveBrowsingContextInContent
)) {
4911 ("Ignored an attempt to set active BrowsingContext [%p] from "
4912 "another process due to a stale action id.",
4916 if (aContext
->IsInProcess()) {
4917 // This message has been in transit for long enough that
4918 // the process association of aContext has changed since
4919 // the other content process sent the message, because
4920 // an iframe in that process became an out-of-process
4921 // iframe while the IPC broadcast that we're receiving
4922 // was in-flight. Let's just ignore this.
4924 ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
4925 "active from another process.",
4929 mActiveBrowsingContextInContentSetFromOtherProcess
= true;
4930 mActiveBrowsingContextInContent
= aContext
;
4931 mActionIdForActiveBrowsingContextInContent
= aActionId
;
4932 MaybeUnlockPointer(aContext
);
4935 void nsFocusManager::UnsetActiveBrowsingContextFromOtherProcess(
4936 BrowsingContext
* aContext
, uint64_t aActionId
) {
4937 MOZ_ASSERT(!XRE_IsParentProcess());
4938 MOZ_ASSERT(aContext
);
4939 if (ActionIdComparableAndLower(aActionId
,
4940 mActionIdForActiveBrowsingContextInContent
)) {
4942 ("Ignored an attempt to unset the active BrowsingContext [%p] from "
4943 "another process due to stale action id.",
4947 if (mActiveBrowsingContextInContent
== aContext
) {
4948 mActiveBrowsingContextInContent
= nullptr;
4949 mActionIdForActiveBrowsingContextInContent
= aActionId
;
4950 MaybeUnlockPointer(nullptr);
4953 ("Ignored an attempt to unset the active BrowsingContext [%p] from "
4959 void nsFocusManager::ReviseActiveBrowsingContext(
4960 uint64_t aOldActionId
, mozilla::dom::BrowsingContext
* aContext
,
4961 uint64_t aNewActionId
) {
4962 MOZ_ASSERT(XRE_IsContentProcess());
4963 if (mActionIdForActiveBrowsingContextInContent
== aOldActionId
) {
4964 mActiveBrowsingContextInContent
= aContext
;
4965 mActionIdForActiveBrowsingContextInContent
= aNewActionId
;
4968 ("Ignored a stale attempt to revise the active BrowsingContext [%p].",
4973 void nsFocusManager::ReviseFocusedBrowsingContext(
4974 uint64_t aOldActionId
, mozilla::dom::BrowsingContext
* aContext
,
4975 uint64_t aNewActionId
) {
4976 MOZ_ASSERT(XRE_IsContentProcess());
4977 if (mActionIdForFocusedBrowsingContextInContent
== aOldActionId
) {
4978 mFocusedBrowsingContextInContent
= aContext
;
4979 mActionIdForFocusedBrowsingContextInContent
= aNewActionId
;
4980 mFocusedElement
= nullptr;
4983 ("Ignored a stale attempt to revise the focused BrowsingContext [%p].",
4988 bool nsFocusManager::SetActiveBrowsingContextInChrome(
4989 mozilla::dom::BrowsingContext
* aContext
, uint64_t aActionId
) {
4990 MOZ_ASSERT(aActionId
);
4991 if (ProcessPendingActiveBrowsingContextActionId(aActionId
, aContext
)) {
4992 MOZ_DIAGNOSTIC_ASSERT(!ActionIdComparableAndLower(
4993 aActionId
, mActionIdForActiveBrowsingContextInChrome
));
4994 mActiveBrowsingContextInChrome
= aContext
;
4995 mActionIdForActiveBrowsingContextInChrome
= aActionId
;
5001 uint64_t nsFocusManager::GetActionIdForActiveBrowsingContextInChrome() const {
5002 return mActionIdForActiveBrowsingContextInChrome
;
5005 uint64_t nsFocusManager::GetActionIdForFocusedBrowsingContextInChrome() const {
5006 return mActionIdForFocusedBrowsingContextInChrome
;
5009 BrowsingContext
* nsFocusManager::GetActiveBrowsingContextInChrome() {
5010 return mActiveBrowsingContextInChrome
;
5013 void nsFocusManager::InsertNewFocusActionId(uint64_t aActionId
) {
5014 MOZ_ASSERT(XRE_IsParentProcess());
5015 MOZ_ASSERT(!mPendingActiveBrowsingContextActions
.Contains(aActionId
));
5016 mPendingActiveBrowsingContextActions
.AppendElement(aActionId
);
5017 MOZ_ASSERT(!mPendingFocusedBrowsingContextActions
.Contains(aActionId
));
5018 mPendingFocusedBrowsingContextActions
.AppendElement(aActionId
);
5021 static void RemoveContentInitiatedActionsUntil(
5022 nsTArray
<uint64_t>& aPendingActions
,
5023 nsTArray
<uint64_t>::index_type aUntil
) {
5024 nsTArray
<uint64_t>::index_type i
= 0;
5025 while (i
< aUntil
) {
5026 auto [actionProc
, actionId
] =
5027 nsContentUtils::SplitProcessSpecificId(aPendingActions
[i
]);
5030 aPendingActions
.RemoveElementAt(i
);
5038 bool nsFocusManager::ProcessPendingActiveBrowsingContextActionId(
5039 uint64_t aActionId
, bool aSettingToNonNull
) {
5040 MOZ_ASSERT(XRE_IsParentProcess());
5041 auto index
= mPendingActiveBrowsingContextActions
.IndexOf(aActionId
);
5042 if (index
== nsTArray
<uint64_t>::NoIndex
) {
5045 // When aSettingToNonNull is true, we need to remove one more
5046 // element to remove the action id itself in addition to
5047 // removing the older ones.
5048 if (aSettingToNonNull
) {
5051 auto [actionProc
, actionId
] =
5052 nsContentUtils::SplitProcessSpecificId(aActionId
);
5055 // Action from content: We allow parent-initiated actions
5056 // to take precedence over content-initiated ones, so we
5057 // remove only prior content-initiated actions.
5058 RemoveContentInitiatedActionsUntil(mPendingActiveBrowsingContextActions
,
5061 // Action from chrome
5062 mPendingActiveBrowsingContextActions
.RemoveElementsAt(0, index
);
5067 bool nsFocusManager::ProcessPendingFocusedBrowsingContextActionId(
5068 uint64_t aActionId
) {
5069 MOZ_ASSERT(XRE_IsParentProcess());
5070 auto index
= mPendingFocusedBrowsingContextActions
.IndexOf(aActionId
);
5071 if (index
== nsTArray
<uint64_t>::NoIndex
) {
5075 auto [actionProc
, actionId
] =
5076 nsContentUtils::SplitProcessSpecificId(aActionId
);
5079 // Action from content: We allow parent-initiated actions
5080 // to take precedence over content-initiated ones, so we
5081 // remove only prior content-initiated actions.
5082 RemoveContentInitiatedActionsUntil(mPendingFocusedBrowsingContextActions
,
5085 // Action from chrome
5086 mPendingFocusedBrowsingContextActions
.RemoveElementsAt(0, index
);
5092 uint64_t nsFocusManager::GenerateFocusActionId() {
5094 nsContentUtils::GenerateProcessSpecificId(++sFocusActionCounter
);
5095 if (XRE_IsParentProcess()) {
5096 nsFocusManager
* fm
= GetFocusManager();
5098 fm
->InsertNewFocusActionId(id
);
5101 mozilla::dom::ContentChild
* contentChild
=
5102 mozilla::dom::ContentChild::GetSingleton();
5103 MOZ_ASSERT(contentChild
);
5104 contentChild
->SendInsertNewFocusActionId(id
);
5109 static bool IsInPointerLockContext(nsPIDOMWindowOuter
* aWin
) {
5110 return PointerLockManager::IsInLockContext(aWin
? aWin
->GetBrowsingContext()
5114 void nsFocusManager::SetFocusedWindowInternal(nsPIDOMWindowOuter
* aWindow
,
5116 bool aSyncBrowsingContext
) {
5117 if (XRE_IsParentProcess() && !PointerUnlocker::sActiveUnlocker
&&
5118 IsInPointerLockContext(mFocusedWindow
) &&
5119 !IsInPointerLockContext(aWindow
)) {
5120 nsCOMPtr
<nsIRunnable
> runnable
= new PointerUnlocker();
5121 NS_DispatchToCurrentThread(runnable
);
5124 // Update the last focus time on any affected documents
5125 if (aWindow
&& aWindow
!= mFocusedWindow
) {
5126 const TimeStamp
now(TimeStamp::Now());
5127 for (Document
* doc
= aWindow
->GetExtantDoc(); doc
;
5128 doc
= doc
->GetInProcessParentDocument()) {
5129 doc
->SetLastFocusTime(now
);
5133 // This function may be called with zero action id to indicate that the
5134 // action id should be ignored.
5135 if (XRE_IsContentProcess() && aActionId
&&
5136 ActionIdComparableAndLower(aActionId
,
5137 mActionIdForFocusedBrowsingContextInContent
)) {
5138 // Unclear if this ever happens.
5140 ("Ignored an attempt to set an in-process BrowsingContext as "
5141 "focused due to stale action id."));
5145 mFocusedWindow
= aWindow
;
5146 BrowsingContext
* bc
= aWindow
? aWindow
->GetBrowsingContext() : nullptr;
5147 if (aSyncBrowsingContext
) {
5148 MOZ_ASSERT(aActionId
,
5149 "aActionId must not be zero if aSyncBrowsingContext is true");
5150 SetFocusedBrowsingContext(bc
, aActionId
);
5151 } else if (XRE_IsContentProcess()) {
5152 MOZ_ASSERT(mFocusedBrowsingContextInContent
== bc
,
5153 "Not syncing BrowsingContext even when different.");
5157 void nsFocusManager::NotifyOfReFocus(nsIContent
& aContent
) {
5158 nsPIDOMWindowOuter
* window
= GetCurrentWindow(&aContent
);
5159 if (!window
|| window
!= mFocusedWindow
) {
5162 if (!aContent
.IsInComposedDoc() || IsNonFocusableRoot(&aContent
)) {
5165 nsIDocShell
* docShell
= window
->GetDocShell();
5169 RefPtr
<PresShell
> presShell
= docShell
->GetPresShell();
5173 nsPresContext
* presContext
= presShell
->GetPresContext();
5177 IMEStateManager::OnReFocus(presContext
, aContent
);
5180 void nsFocusManager::MarkUncollectableForCCGeneration(uint32_t aGeneration
) {
5185 if (sInstance
->mActiveWindow
) {
5186 sInstance
->mActiveWindow
->MarkUncollectableForCCGeneration(aGeneration
);
5188 if (sInstance
->mFocusedWindow
) {
5189 sInstance
->mFocusedWindow
->MarkUncollectableForCCGeneration(aGeneration
);
5191 if (sInstance
->mWindowBeingLowered
) {
5192 sInstance
->mWindowBeingLowered
->MarkUncollectableForCCGeneration(
5195 if (sInstance
->mFocusedElement
) {
5196 sInstance
->mFocusedElement
->OwnerDoc()->MarkUncollectableForCCGeneration(
5199 if (sInstance
->mFirstBlurEvent
) {
5200 sInstance
->mFirstBlurEvent
->OwnerDoc()->MarkUncollectableForCCGeneration(
5203 if (sInstance
->mFirstFocusEvent
) {
5204 sInstance
->mFirstFocusEvent
->OwnerDoc()->MarkUncollectableForCCGeneration(
5209 bool nsFocusManager::CanSkipFocus(nsIContent
* aContent
) {
5210 if (!aContent
|| nsContentUtils::IsChromeDoc(aContent
->OwnerDoc())) {
5214 if (mFocusedElement
== aContent
) {
5218 nsIDocShell
* ds
= aContent
->OwnerDoc()->GetDocShell();
5223 if (XRE_IsParentProcess()) {
5224 nsCOMPtr
<nsIDocShellTreeItem
> root
;
5225 ds
->GetInProcessRootTreeItem(getter_AddRefs(root
));
5226 nsCOMPtr
<nsPIDOMWindowOuter
> newRootWindow
=
5227 root
? root
->GetWindow() : nullptr;
5228 if (mActiveWindow
!= newRootWindow
) {
5229 nsPIDOMWindowOuter
* outerWindow
= aContent
->OwnerDoc()->GetWindow();
5230 if (outerWindow
&& outerWindow
->GetFocusedElement() == aContent
) {
5235 BrowsingContext
* bc
= aContent
->OwnerDoc()->GetBrowsingContext();
5236 BrowsingContext
* top
= bc
? bc
->Top() : nullptr;
5237 if (GetActiveBrowsingContext() != top
) {
5238 nsPIDOMWindowOuter
* outerWindow
= aContent
->OwnerDoc()->GetWindow();
5239 if (outerWindow
&& outerWindow
->GetFocusedElement() == aContent
) {
5248 nsresult
NS_NewFocusManager(nsIFocusManager
** aResult
) {
5249 NS_IF_ADDREF(*aResult
= nsFocusManager::GetFocusManager());