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