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