Bug 1842773 - Part 5: Add ArrayBuffer.prototype.{maxByteLength,resizable} getters...
[gecko.git] / dom / base / nsFocusManager.cpp
blob60f45157cdfceb7ebf4f9a1681d0e59dc1822360
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/BrowserBridgeChild.h"
57 #include "mozilla/dom/Text.h"
58 #include "mozilla/dom/XULPopupElement.h"
59 #include "mozilla/dom/WindowGlobalParent.h"
60 #include "mozilla/dom/WindowGlobalChild.h"
61 #include "mozilla/EventDispatcher.h"
62 #include "mozilla/EventStateManager.h"
63 #include "mozilla/HTMLEditor.h"
64 #include "mozilla/IMEStateManager.h"
65 #include "mozilla/LookAndFeel.h"
66 #include "mozilla/Maybe.h"
67 #include "mozilla/PointerLockManager.h"
68 #include "mozilla/Preferences.h"
69 #include "mozilla/PresShell.h"
70 #include "mozilla/Services.h"
71 #include "mozilla/Unused.h"
72 #include "mozilla/StaticPrefs_full_screen_api.h"
73 #include "mozilla/Try.h"
74 #include "mozilla/widget/IMEData.h"
75 #include <algorithm>
77 #include "nsIDOMXULMenuListElement.h"
79 #ifdef ACCESSIBILITY
80 # include "nsAccessibilityService.h"
81 #endif
83 using namespace mozilla;
84 using namespace mozilla::dom;
85 using namespace mozilla::widget;
87 // Two types of focus pr logging are available:
88 // 'Focus' for normal focus manager calls
89 // 'FocusNavigation' for tab and document navigation
90 LazyLogModule gFocusLog("Focus");
91 LazyLogModule gFocusNavigationLog("FocusNavigation");
93 #define LOGFOCUS(args) MOZ_LOG(gFocusLog, mozilla::LogLevel::Debug, args)
94 #define LOGFOCUSNAVIGATION(args) \
95 MOZ_LOG(gFocusNavigationLog, mozilla::LogLevel::Debug, args)
97 #define LOGTAG(log, format, content) \
98 if (MOZ_LOG_TEST(log, LogLevel::Debug)) { \
99 nsAutoCString tag("(none)"_ns); \
100 if (content) { \
101 content->NodeInfo()->NameAtom()->ToUTF8String(tag); \
103 MOZ_LOG(log, LogLevel::Debug, (format, tag.get())); \
106 #define LOGCONTENT(format, content) LOGTAG(gFocusLog, format, content)
107 #define LOGCONTENTNAVIGATION(format, content) \
108 LOGTAG(gFocusNavigationLog, format, content)
110 struct nsDelayedBlurOrFocusEvent {
111 nsDelayedBlurOrFocusEvent(EventMessage aEventMessage, PresShell* aPresShell,
112 Document* aDocument, EventTarget* aTarget,
113 EventTarget* aRelatedTarget)
114 : mPresShell(aPresShell),
115 mDocument(aDocument),
116 mTarget(aTarget),
117 mEventMessage(aEventMessage),
118 mRelatedTarget(aRelatedTarget) {}
120 nsDelayedBlurOrFocusEvent(const nsDelayedBlurOrFocusEvent& aOther)
121 : mPresShell(aOther.mPresShell),
122 mDocument(aOther.mDocument),
123 mTarget(aOther.mTarget),
124 mEventMessage(aOther.mEventMessage) {}
126 RefPtr<PresShell> mPresShell;
127 nsCOMPtr<Document> mDocument;
128 nsCOMPtr<EventTarget> mTarget;
129 EventMessage mEventMessage;
130 nsCOMPtr<EventTarget> mRelatedTarget;
133 inline void ImplCycleCollectionUnlink(nsDelayedBlurOrFocusEvent& aField) {
134 aField.mPresShell = nullptr;
135 aField.mDocument = nullptr;
136 aField.mTarget = nullptr;
137 aField.mRelatedTarget = nullptr;
140 inline void ImplCycleCollectionTraverse(
141 nsCycleCollectionTraversalCallback& aCallback,
142 nsDelayedBlurOrFocusEvent& aField, const char* aName, uint32_t aFlags = 0) {
143 CycleCollectionNoteChild(
144 aCallback, static_cast<nsIDocumentObserver*>(aField.mPresShell.get()),
145 aName, aFlags);
146 CycleCollectionNoteChild(aCallback, aField.mDocument.get(), aName, aFlags);
147 CycleCollectionNoteChild(aCallback, aField.mTarget.get(), aName, aFlags);
148 CycleCollectionNoteChild(aCallback, aField.mRelatedTarget.get(), aName,
149 aFlags);
152 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFocusManager)
153 NS_INTERFACE_MAP_ENTRY(nsIFocusManager)
154 NS_INTERFACE_MAP_ENTRY(nsIObserver)
155 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
156 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFocusManager)
157 NS_INTERFACE_MAP_END
159 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFocusManager)
160 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFocusManager)
162 NS_IMPL_CYCLE_COLLECTION_WEAK(nsFocusManager, mActiveWindow,
163 mActiveBrowsingContextInContent,
164 mActiveBrowsingContextInChrome, mFocusedWindow,
165 mFocusedBrowsingContextInContent,
166 mFocusedBrowsingContextInChrome, mFocusedElement,
167 mFirstBlurEvent, mFirstFocusEvent,
168 mWindowBeingLowered, mDelayedBlurFocusEvents)
170 StaticRefPtr<nsFocusManager> nsFocusManager::sInstance;
171 bool nsFocusManager::sTestMode = false;
172 uint64_t nsFocusManager::sFocusActionCounter = 0;
174 static const char* kObservedPrefs[] = {"accessibility.browsewithcaret",
175 "accessibility.tabfocus_applies_to_xul",
176 "focusmanager.testmode", nullptr};
178 nsFocusManager::nsFocusManager()
179 : mActionIdForActiveBrowsingContextInContent(0),
180 mActionIdForActiveBrowsingContextInChrome(0),
181 mActionIdForFocusedBrowsingContextInContent(0),
182 mActionIdForFocusedBrowsingContextInChrome(0),
183 mActiveBrowsingContextInContentSetFromOtherProcess(false),
184 mEventHandlingNeedsFlush(false) {}
186 nsFocusManager::~nsFocusManager() {
187 Preferences::UnregisterCallbacks(nsFocusManager::PrefChanged, kObservedPrefs,
188 this);
190 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
191 if (obs) {
192 obs->RemoveObserver(this, "xpcom-shutdown");
196 // static
197 nsresult nsFocusManager::Init() {
198 sInstance = new nsFocusManager();
200 nsIContent::sTabFocusModelAppliesToXUL =
201 Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
202 nsIContent::sTabFocusModelAppliesToXUL);
204 sTestMode = Preferences::GetBool("focusmanager.testmode", false);
206 Preferences::RegisterCallbacks(nsFocusManager::PrefChanged, kObservedPrefs,
207 sInstance.get());
209 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
210 if (obs) {
211 obs->AddObserver(sInstance, "xpcom-shutdown", true);
214 return NS_OK;
217 // static
218 void nsFocusManager::Shutdown() { sInstance = nullptr; }
220 // static
221 void nsFocusManager::PrefChanged(const char* aPref, void* aSelf) {
222 if (RefPtr<nsFocusManager> fm = static_cast<nsFocusManager*>(aSelf)) {
223 fm->PrefChanged(aPref);
227 void nsFocusManager::PrefChanged(const char* aPref) {
228 nsDependentCString pref(aPref);
229 if (pref.EqualsLiteral("accessibility.browsewithcaret")) {
230 UpdateCaretForCaretBrowsingMode();
231 } else if (pref.EqualsLiteral("accessibility.tabfocus_applies_to_xul")) {
232 nsIContent::sTabFocusModelAppliesToXUL =
233 Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
234 nsIContent::sTabFocusModelAppliesToXUL);
235 } else if (pref.EqualsLiteral("focusmanager.testmode")) {
236 sTestMode = Preferences::GetBool("focusmanager.testmode", false);
240 NS_IMETHODIMP
241 nsFocusManager::Observe(nsISupports* aSubject, const char* aTopic,
242 const char16_t* aData) {
243 if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
244 mActiveWindow = nullptr;
245 mActiveBrowsingContextInContent = nullptr;
246 mActionIdForActiveBrowsingContextInContent = 0;
247 mActionIdForFocusedBrowsingContextInContent = 0;
248 mActiveBrowsingContextInChrome = nullptr;
249 mActionIdForActiveBrowsingContextInChrome = 0;
250 mActionIdForFocusedBrowsingContextInChrome = 0;
251 mFocusedWindow = nullptr;
252 mFocusedBrowsingContextInContent = nullptr;
253 mFocusedBrowsingContextInChrome = nullptr;
254 mFocusedElement = nullptr;
255 mFirstBlurEvent = nullptr;
256 mFirstFocusEvent = nullptr;
257 mWindowBeingLowered = nullptr;
258 mDelayedBlurFocusEvents.Clear();
261 return NS_OK;
264 static bool ActionIdComparableAndLower(uint64_t aActionId,
265 uint64_t aReference) {
266 MOZ_ASSERT(aActionId, "Uninitialized action id");
267 auto [actionProc, actionId] =
268 nsContentUtils::SplitProcessSpecificId(aActionId);
269 auto [refProc, refId] = nsContentUtils::SplitProcessSpecificId(aReference);
270 return actionProc == refProc && actionId < refId;
273 // given a frame content node, retrieve the nsIDOMWindow displayed in it
274 static nsPIDOMWindowOuter* GetContentWindow(nsIContent* aContent) {
275 Document* doc = aContent->GetComposedDoc();
276 if (doc) {
277 Document* subdoc = doc->GetSubDocumentFor(aContent);
278 if (subdoc) {
279 return subdoc->GetWindow();
283 return nullptr;
286 bool nsFocusManager::IsFocused(nsIContent* aContent) {
287 if (!aContent || !mFocusedElement) {
288 return false;
290 return aContent == mFocusedElement;
293 bool nsFocusManager::IsTestMode() { return sTestMode; }
295 bool nsFocusManager::IsInActiveWindow(BrowsingContext* aBC) const {
296 RefPtr<BrowsingContext> top = aBC->Top();
297 if (XRE_IsParentProcess()) {
298 top = top->Canonical()->TopCrossChromeBoundary();
300 return IsSameOrAncestor(top, GetActiveBrowsingContext());
303 // get the current window for the given content node
304 static nsPIDOMWindowOuter* GetCurrentWindow(nsIContent* aContent) {
305 Document* doc = aContent->GetComposedDoc();
306 return doc ? doc->GetWindow() : nullptr;
309 // static
310 Element* nsFocusManager::GetFocusedDescendant(
311 nsPIDOMWindowOuter* aWindow, SearchRange aSearchRange,
312 nsPIDOMWindowOuter** aFocusedWindow) {
313 NS_ENSURE_TRUE(aWindow, nullptr);
315 *aFocusedWindow = nullptr;
317 Element* currentElement = nullptr;
318 nsPIDOMWindowOuter* window = aWindow;
319 for (;;) {
320 *aFocusedWindow = window;
321 currentElement = window->GetFocusedElement();
322 if (!currentElement || aSearchRange == eOnlyCurrentWindow) {
323 break;
326 window = GetContentWindow(currentElement);
327 if (!window) {
328 break;
331 if (aSearchRange == eIncludeAllDescendants) {
332 continue;
335 MOZ_ASSERT(aSearchRange == eIncludeVisibleDescendants);
337 // If the child window doesn't have PresShell, it means the window is
338 // invisible.
339 nsIDocShell* docShell = window->GetDocShell();
340 if (!docShell) {
341 break;
343 if (!docShell->GetPresShell()) {
344 break;
348 NS_IF_ADDREF(*aFocusedWindow);
350 return currentElement;
353 // static
354 InputContextAction::Cause nsFocusManager::GetFocusMoveActionCause(
355 uint32_t aFlags) {
356 if (aFlags & nsIFocusManager::FLAG_BYTOUCH) {
357 return InputContextAction::CAUSE_TOUCH;
358 } else if (aFlags & nsIFocusManager::FLAG_BYMOUSE) {
359 return InputContextAction::CAUSE_MOUSE;
360 } else if (aFlags & nsIFocusManager::FLAG_BYKEY) {
361 return InputContextAction::CAUSE_KEY;
362 } else if (aFlags & nsIFocusManager::FLAG_BYLONGPRESS) {
363 return InputContextAction::CAUSE_LONGPRESS;
365 return InputContextAction::CAUSE_UNKNOWN;
368 NS_IMETHODIMP
369 nsFocusManager::GetActiveWindow(mozIDOMWindowProxy** aWindow) {
370 MOZ_ASSERT(XRE_IsParentProcess(),
371 "Must not be called outside the parent process.");
372 NS_IF_ADDREF(*aWindow = mActiveWindow);
373 return NS_OK;
376 NS_IMETHODIMP
377 nsFocusManager::GetActiveBrowsingContext(BrowsingContext** aBrowsingContext) {
378 NS_IF_ADDREF(*aBrowsingContext = GetActiveBrowsingContext());
379 return NS_OK;
382 void nsFocusManager::FocusWindow(nsPIDOMWindowOuter* aWindow,
383 CallerType aCallerType) {
384 if (RefPtr<nsFocusManager> fm = sInstance) {
385 fm->SetFocusedWindowWithCallerType(aWindow, aCallerType);
389 NS_IMETHODIMP
390 nsFocusManager::GetFocusedWindow(mozIDOMWindowProxy** aFocusedWindow) {
391 NS_IF_ADDREF(*aFocusedWindow = mFocusedWindow);
392 return NS_OK;
395 NS_IMETHODIMP
396 nsFocusManager::GetFocusedContentBrowsingContext(
397 BrowsingContext** aBrowsingContext) {
398 MOZ_DIAGNOSTIC_ASSERT(
399 XRE_IsParentProcess(),
400 "We only have use cases for this in the parent process");
401 NS_IF_ADDREF(*aBrowsingContext = GetFocusedBrowsingContextInChrome());
402 return NS_OK;
405 nsresult nsFocusManager::SetFocusedWindowWithCallerType(
406 mozIDOMWindowProxy* aWindowToFocus, CallerType aCallerType) {
407 LOGFOCUS(("<<SetFocusedWindow begin>>"));
409 nsCOMPtr<nsPIDOMWindowOuter> windowToFocus =
410 nsPIDOMWindowOuter::From(aWindowToFocus);
411 NS_ENSURE_TRUE(windowToFocus, NS_ERROR_FAILURE);
413 nsCOMPtr<Element> frameElement = windowToFocus->GetFrameElementInternal();
414 Maybe<uint64_t> actionIdFromSetFocusInner;
415 if (frameElement) {
416 // pass false for aFocusChanged so that the caret does not get updated
417 // and scrolling does not occur.
418 actionIdFromSetFocusInner = SetFocusInner(frameElement, 0, false, true);
419 } else {
420 // this is a top-level window. If the window has a child frame focused,
421 // clear the focus. Otherwise, focus should already be in this frame, or
422 // already cleared. This ensures that focus will be in this frame and not
423 // in a child.
424 nsIContent* content = windowToFocus->GetFocusedElement();
425 if (content) {
426 if (nsCOMPtr<nsPIDOMWindowOuter> childWindow = GetContentWindow(content))
427 ClearFocus(windowToFocus);
431 nsCOMPtr<nsPIDOMWindowOuter> rootWindow = windowToFocus->GetPrivateRoot();
432 const uint64_t actionId = actionIdFromSetFocusInner.isSome()
433 ? actionIdFromSetFocusInner.value()
434 : sInstance->GenerateFocusActionId();
435 if (rootWindow) {
436 RaiseWindow(rootWindow, aCallerType, actionId);
439 LOGFOCUS(("<<SetFocusedWindow end actionid: %" PRIu64 ">>", actionId));
441 return NS_OK;
444 NS_IMETHODIMP nsFocusManager::SetFocusedWindow(
445 mozIDOMWindowProxy* aWindowToFocus) {
446 return SetFocusedWindowWithCallerType(aWindowToFocus, CallerType::System);
449 NS_IMETHODIMP
450 nsFocusManager::GetFocusedElement(Element** aFocusedElement) {
451 RefPtr<Element> focusedElement = mFocusedElement;
452 focusedElement.forget(aFocusedElement);
453 return NS_OK;
456 uint32_t nsFocusManager::GetLastFocusMethod(nsPIDOMWindowOuter* aWindow) const {
457 nsPIDOMWindowOuter* window = aWindow ? aWindow : mFocusedWindow.get();
458 uint32_t method = window ? window->GetFocusMethod() : 0;
459 NS_ASSERTION((method & METHOD_MASK) == method, "invalid focus method");
460 return method;
463 NS_IMETHODIMP
464 nsFocusManager::GetLastFocusMethod(mozIDOMWindowProxy* aWindow,
465 uint32_t* aLastFocusMethod) {
466 *aLastFocusMethod = GetLastFocusMethod(nsPIDOMWindowOuter::From(aWindow));
467 return NS_OK;
470 NS_IMETHODIMP
471 nsFocusManager::SetFocus(Element* aElement, uint32_t aFlags) {
472 LOGFOCUS(("<<SetFocus begin>>"));
474 NS_ENSURE_ARG(aElement);
476 SetFocusInner(aElement, aFlags, true, true);
478 LOGFOCUS(("<<SetFocus end>>"));
480 return NS_OK;
483 NS_IMETHODIMP
484 nsFocusManager::ElementIsFocusable(Element* aElement, uint32_t aFlags,
485 bool* aIsFocusable) {
486 NS_ENSURE_TRUE(aElement, NS_ERROR_INVALID_ARG);
487 *aIsFocusable = !!FlushAndCheckIfFocusable(aElement, aFlags);
488 return NS_OK;
491 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
492 nsFocusManager::MoveFocus(mozIDOMWindowProxy* aWindow, Element* aStartElement,
493 uint32_t aType, uint32_t aFlags, Element** aElement) {
494 *aElement = nullptr;
496 LOGFOCUS(("<<MoveFocus begin Type: %d Flags: %x>>", aType, aFlags));
498 if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug) && mFocusedWindow) {
499 Document* doc = mFocusedWindow->GetExtantDoc();
500 if (doc && doc->GetDocumentURI()) {
501 LOGFOCUS((" Focused Window: %p %s", mFocusedWindow.get(),
502 doc->GetDocumentURI()->GetSpecOrDefault().get()));
506 LOGCONTENT(" Current Focus: %s", mFocusedElement.get());
508 // use FLAG_BYMOVEFOCUS when switching focus with MoveFocus unless one of
509 // the other focus methods is already set, or we're just moving to the root
510 // or caret position.
511 if (aType != MOVEFOCUS_ROOT && aType != MOVEFOCUS_CARET &&
512 (aFlags & METHOD_MASK) == 0) {
513 aFlags |= FLAG_BYMOVEFOCUS;
516 nsCOMPtr<nsPIDOMWindowOuter> window;
517 if (aStartElement) {
518 window = GetCurrentWindow(aStartElement);
519 } else {
520 window = aWindow ? nsPIDOMWindowOuter::From(aWindow) : mFocusedWindow.get();
523 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
525 // Flush to ensure that focusability of descendants is computed correctly.
526 if (RefPtr<Document> doc = window->GetExtantDoc()) {
527 doc->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames);
530 bool noParentTraversal = aFlags & FLAG_NOPARENTFRAME;
531 nsCOMPtr<nsIContent> newFocus;
532 nsresult rv = DetermineElementToMoveFocus(window, aStartElement, aType,
533 noParentTraversal, true,
534 getter_AddRefs(newFocus));
535 if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
536 return NS_OK;
539 NS_ENSURE_SUCCESS(rv, rv);
541 LOGCONTENTNAVIGATION("Element to be focused: %s", newFocus.get());
543 if (newFocus && newFocus->IsElement()) {
544 // for caret movement, pass false for the aFocusChanged argument,
545 // otherwise the caret will end up moving to the focus position. This
546 // would be a problem because the caret would move to the beginning of the
547 // focused link making it impossible to navigate the caret over a link.
548 SetFocusInner(MOZ_KnownLive(newFocus->AsElement()), aFlags,
549 aType != MOVEFOCUS_CARET, true);
550 *aElement = do_AddRef(newFocus->AsElement()).take();
551 } else if (aType == MOVEFOCUS_ROOT || aType == MOVEFOCUS_CARET) {
552 // no content was found, so clear the focus for these two types.
553 ClearFocus(window);
556 LOGFOCUS(("<<MoveFocus end>>"));
558 return NS_OK;
561 NS_IMETHODIMP
562 nsFocusManager::ClearFocus(mozIDOMWindowProxy* aWindow) {
563 LOGFOCUS(("<<ClearFocus begin>>"));
565 // if the window to clear is the focused window or an ancestor of the
566 // focused window, then blur the existing focused content. Otherwise, the
567 // focus is somewhere else so just update the current node.
568 NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
569 nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
571 if (IsSameOrAncestor(window, GetFocusedBrowsingContext())) {
572 RefPtr<BrowsingContext> bc = window->GetBrowsingContext();
573 bool isAncestor = (GetFocusedBrowsingContext() != bc);
574 uint64_t actionId = GenerateFocusActionId();
575 if (Blur(bc, nullptr, isAncestor, true, false, actionId)) {
576 // if we are clearing the focus on an ancestor of the focused window,
577 // the ancestor will become the new focused window, so focus it
578 if (isAncestor) {
579 Focus(window, nullptr, 0, true, false, false, true, actionId);
582 } else {
583 window->SetFocusedElement(nullptr);
586 LOGFOCUS(("<<ClearFocus end>>"));
588 return NS_OK;
591 NS_IMETHODIMP
592 nsFocusManager::GetFocusedElementForWindow(mozIDOMWindowProxy* aWindow,
593 bool aDeep,
594 mozIDOMWindowProxy** aFocusedWindow,
595 Element** aElement) {
596 *aElement = nullptr;
597 if (aFocusedWindow) {
598 *aFocusedWindow = nullptr;
601 NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
602 nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
604 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
605 RefPtr<Element> focusedElement =
606 GetFocusedDescendant(window,
607 aDeep ? nsFocusManager::eIncludeAllDescendants
608 : nsFocusManager::eOnlyCurrentWindow,
609 getter_AddRefs(focusedWindow));
611 focusedElement.forget(aElement);
613 if (aFocusedWindow) {
614 NS_IF_ADDREF(*aFocusedWindow = focusedWindow);
617 return NS_OK;
620 NS_IMETHODIMP
621 nsFocusManager::MoveCaretToFocus(mozIDOMWindowProxy* aWindow) {
622 nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(aWindow);
623 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav);
624 if (dsti) {
625 if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) {
626 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(dsti);
627 NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
629 // don't move the caret for editable documents
630 bool isEditable;
631 docShell->GetEditable(&isEditable);
632 if (isEditable) {
633 return NS_OK;
636 RefPtr<PresShell> presShell = docShell->GetPresShell();
637 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
639 nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
640 if (RefPtr<Element> focusedElement = window->GetFocusedElement()) {
641 MoveCaretToFocus(presShell, focusedElement);
646 return NS_OK;
649 void nsFocusManager::WindowRaised(mozIDOMWindowProxy* aWindow,
650 uint64_t aActionId) {
651 if (!aWindow) {
652 return;
655 nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
656 BrowsingContext* bc = window->GetBrowsingContext();
658 if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
659 LOGFOCUS(("Window %p Raised [Currently: %p %p] actionid: %" PRIu64, aWindow,
660 mActiveWindow.get(), mFocusedWindow.get(), aActionId));
661 Document* doc = window->GetExtantDoc();
662 if (doc && doc->GetDocumentURI()) {
663 LOGFOCUS((" Raised Window: %p %s", aWindow,
664 doc->GetDocumentURI()->GetSpecOrDefault().get()));
666 if (mActiveWindow) {
667 doc = mActiveWindow->GetExtantDoc();
668 if (doc && doc->GetDocumentURI()) {
669 LOGFOCUS((" Active Window: %p %s", mActiveWindow.get(),
670 doc->GetDocumentURI()->GetSpecOrDefault().get()));
675 if (XRE_IsParentProcess()) {
676 if (mActiveWindow == window) {
677 // The window is already active, so there is no need to focus anything,
678 // but make sure that the right widget is focused. This is a special case
679 // for Windows because when restoring a minimized window, a second
680 // activation will occur and the top-level widget could be focused instead
681 // of the child we want. We solve this by calling SetFocus to ensure that
682 // what the focus manager thinks should be the current widget is actually
683 // focused.
684 EnsureCurrentWidgetFocused(CallerType::System);
685 return;
688 // lower the existing window, if any. This shouldn't happen usually.
689 if (nsCOMPtr<nsPIDOMWindowOuter> activeWindow = mActiveWindow) {
690 WindowLowered(activeWindow, aActionId);
692 } else if (bc->IsTop()) {
693 BrowsingContext* active = GetActiveBrowsingContext();
694 if (active == bc && !mActiveBrowsingContextInContentSetFromOtherProcess) {
695 // EnsureCurrentWidgetFocused() should not be necessary with
696 // PuppetWidget.
697 return;
700 if (active && active != bc) {
701 if (active->IsInProcess()) {
702 nsCOMPtr<nsPIDOMWindowOuter> activeWindow = active->GetDOMWindow();
703 WindowLowered(activeWindow, aActionId);
705 // No else, because trying to lower other-process windows
706 // from here can result in the BrowsingContext no longer
707 // existing in the parent process by the time it deserializes
708 // the IPC message.
712 nsCOMPtr<nsIDocShellTreeItem> docShellAsItem = window->GetDocShell();
713 // If there's no docShellAsItem, this window must have been closed,
714 // in that case there is no tree owner.
715 if (!docShellAsItem) {
716 return;
719 // set this as the active window
720 if (XRE_IsParentProcess()) {
721 mActiveWindow = window;
722 } else if (bc->IsTop()) {
723 SetActiveBrowsingContextInContent(bc, aActionId);
726 // ensure that the window is enabled and visible
727 nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
728 docShellAsItem->GetTreeOwner(getter_AddRefs(treeOwner));
729 nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner);
730 if (baseWindow) {
731 bool isEnabled = true;
732 if (NS_SUCCEEDED(baseWindow->GetEnabled(&isEnabled)) && !isEnabled) {
733 return;
736 baseWindow->SetVisibility(true);
739 if (XRE_IsParentProcess()) {
740 // Unsetting top-level focus upon lowering was inhibited to accommodate
741 // ATOK, so we need to do it here.
742 BrowserParent::UnsetTopLevelWebFocusAll();
743 ActivateOrDeactivate(window, true);
746 // retrieve the last focused element within the window that was raised
747 nsCOMPtr<nsPIDOMWindowOuter> currentWindow;
748 RefPtr<Element> currentFocus = GetFocusedDescendant(
749 window, eIncludeAllDescendants, getter_AddRefs(currentWindow));
751 NS_ASSERTION(currentWindow, "window raised with no window current");
752 if (!currentWindow) {
753 return;
756 nsCOMPtr<nsIAppWindow> appWin(do_GetInterface(baseWindow));
757 // We use mFocusedWindow here is basically for the case that iframe navigate
758 // from a.com to b.com for example, so it ends up being loaded in a different
759 // process after Fission, but
760 // currentWindow->GetBrowsingContext() == GetFocusedBrowsingContext() would
761 // still be true because focused browsing context is synced, and we won't
762 // fire a focus event while focusing if we use it as condition.
763 Focus(currentWindow, currentFocus, 0, currentWindow != mFocusedWindow, false,
764 appWin != nullptr, true, aActionId);
767 void nsFocusManager::WindowLowered(mozIDOMWindowProxy* aWindow,
768 uint64_t aActionId) {
769 if (!aWindow) {
770 return;
773 nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
775 if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
776 LOGFOCUS(("Window %p Lowered [Currently: %p %p]", aWindow,
777 mActiveWindow.get(), mFocusedWindow.get()));
778 Document* doc = window->GetExtantDoc();
779 if (doc && doc->GetDocumentURI()) {
780 LOGFOCUS((" Lowered Window: %s",
781 doc->GetDocumentURI()->GetSpecOrDefault().get()));
783 if (mActiveWindow) {
784 doc = mActiveWindow->GetExtantDoc();
785 if (doc && doc->GetDocumentURI()) {
786 LOGFOCUS((" Active Window: %s",
787 doc->GetDocumentURI()->GetSpecOrDefault().get()));
792 if (XRE_IsParentProcess()) {
793 if (mActiveWindow != window) {
794 return;
796 } else {
797 BrowsingContext* bc = window->GetBrowsingContext();
798 BrowsingContext* active = GetActiveBrowsingContext();
799 if (active != bc->Top()) {
800 return;
804 // clear the mouse capture as the active window has changed
805 PresShell::ReleaseCapturingContent();
807 // In addition, reset the drag state to ensure that we are no longer in
808 // drag-select mode.
809 if (mFocusedWindow) {
810 nsCOMPtr<nsIDocShell> docShell = mFocusedWindow->GetDocShell();
811 if (docShell) {
812 if (PresShell* presShell = docShell->GetPresShell()) {
813 RefPtr<nsFrameSelection> frameSelection = presShell->FrameSelection();
814 frameSelection->SetDragState(false);
819 if (XRE_IsParentProcess()) {
820 ActivateOrDeactivate(window, false);
823 // keep track of the window being lowered, so that attempts to raise the
824 // window can be prevented until we return. Otherwise, focus can get into
825 // an unusual state.
826 mWindowBeingLowered = window;
827 if (XRE_IsParentProcess()) {
828 mActiveWindow = nullptr;
829 } else {
830 BrowsingContext* bc = window->GetBrowsingContext();
831 if (bc == bc->Top()) {
832 SetActiveBrowsingContextInContent(nullptr, aActionId);
836 if (mFocusedWindow) {
837 Blur(nullptr, nullptr, true, true, false, aActionId);
840 mWindowBeingLowered = nullptr;
843 nsresult nsFocusManager::ContentRemoved(Document* aDocument,
844 nsIContent* aContent) {
845 NS_ENSURE_ARG(aDocument);
846 NS_ENSURE_ARG(aContent);
848 RefPtr<nsPIDOMWindowOuter> window = aDocument->GetWindow();
849 if (!window) {
850 return NS_OK;
853 // if the content is currently focused in the window, or is an
854 // shadow-including inclusive ancestor of the currently focused element,
855 // reset the focus within that window.
856 RefPtr<Element> previousFocusedElement = window->GetFocusedElement();
857 if (!previousFocusedElement) {
858 return NS_OK;
861 if (!nsContentUtils::ContentIsHostIncludingDescendantOf(
862 previousFocusedElement, aContent)) {
863 return NS_OK;
866 RefPtr<Element> newFocusedElement = [&]() -> Element* {
867 if (auto* sr = ShadowRoot::FromNode(aContent)) {
868 if (sr->IsUAWidget() && sr->Host()->IsHTMLElement(nsGkAtoms::input)) {
869 return sr->Host();
872 return nullptr;
873 }();
875 window->SetFocusedElement(newFocusedElement);
877 // if this window is currently focused, clear the global focused
878 // element as well, but don't fire any events.
879 if (window->GetBrowsingContext() == GetFocusedBrowsingContext()) {
880 mFocusedElement = newFocusedElement;
881 } else if (Document* subdoc =
882 aDocument->GetSubDocumentFor(previousFocusedElement)) {
883 // Check if the node that was focused is an iframe or similar by looking if
884 // it has a subdocument. This would indicate that this focused iframe
885 // and its descendants will be going away. We will need to move the focus
886 // somewhere else, so just clear the focus in the toplevel window so that no
887 // element is focused.
889 // The Fission case is handled in FlushAndCheckIfFocusable().
890 if (nsCOMPtr<nsIDocShell> docShell = subdoc->GetDocShell()) {
891 nsCOMPtr<nsPIDOMWindowOuter> childWindow = docShell->GetWindow();
892 if (childWindow &&
893 IsSameOrAncestor(childWindow, GetFocusedBrowsingContext())) {
894 if (XRE_IsParentProcess()) {
895 nsCOMPtr<nsPIDOMWindowOuter> activeWindow = mActiveWindow;
896 ClearFocus(activeWindow);
897 } else {
898 BrowsingContext* active = GetActiveBrowsingContext();
899 if (active) {
900 if (active->IsInProcess()) {
901 nsCOMPtr<nsPIDOMWindowOuter> activeWindow =
902 active->GetDOMWindow();
903 ClearFocus(activeWindow);
904 } else {
905 mozilla::dom::ContentChild* contentChild =
906 mozilla::dom::ContentChild::GetSingleton();
907 MOZ_ASSERT(contentChild);
908 contentChild->SendClearFocus(active);
910 } // no else, because ClearFocus does nothing with nullptr
916 // Notify the editor in case we removed its ancestor limiter.
917 if (previousFocusedElement->IsEditable()) {
918 if (nsCOMPtr<nsIDocShell> docShell = aDocument->GetDocShell()) {
919 if (RefPtr<HTMLEditor> htmlEditor = docShell->GetHTMLEditor()) {
920 RefPtr<Selection> selection = htmlEditor->GetSelection();
921 if (selection && selection->GetFrameSelection() &&
922 previousFocusedElement ==
923 selection->GetFrameSelection()->GetAncestorLimiter()) {
924 htmlEditor->FinalizeSelection();
930 if (!newFocusedElement) {
931 NotifyFocusStateChange(previousFocusedElement, newFocusedElement, 0,
932 /* aGettingFocus = */ false, false);
933 } else {
934 // We should already have the right state, which is managed by the <input>
935 // widget.
936 MOZ_ASSERT(newFocusedElement->State().HasState(ElementState::FOCUS));
939 // If we changed focused element and the element still has focus, let's
940 // notify IME of focus. Note that if new focus move has already occurred
941 // by running script, we should not let IMEStateManager of outdated focus
942 // change.
943 if (mFocusedElement == newFocusedElement && mFocusedWindow == window) {
944 RefPtr<nsPresContext> presContext(aDocument->GetPresContext());
945 IMEStateManager::OnChangeFocus(presContext, newFocusedElement,
946 InputContextAction::Cause::CAUSE_UNKNOWN);
949 return NS_OK;
952 void nsFocusManager::WindowShown(mozIDOMWindowProxy* aWindow,
953 bool aNeedsFocus) {
954 if (!aWindow) {
955 return;
958 nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
960 if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
961 LOGFOCUS(("Window %p Shown [Currently: %p %p]", window.get(),
962 mActiveWindow.get(), mFocusedWindow.get()));
963 Document* doc = window->GetExtantDoc();
964 if (doc && doc->GetDocumentURI()) {
965 LOGFOCUS(("Shown Window: %s",
966 doc->GetDocumentURI()->GetSpecOrDefault().get()));
969 if (mFocusedWindow) {
970 doc = mFocusedWindow->GetExtantDoc();
971 if (doc && doc->GetDocumentURI()) {
972 LOGFOCUS((" Focused Window: %s",
973 doc->GetDocumentURI()->GetSpecOrDefault().get()));
978 if (XRE_IsParentProcess()) {
979 if (BrowsingContext* bc = window->GetBrowsingContext()) {
980 if (bc->IsTop()) {
981 bc->SetIsActiveBrowserWindow(bc->GetIsActiveBrowserWindow());
986 if (XRE_IsParentProcess()) {
987 if (mFocusedWindow != window) {
988 return;
990 } else {
991 BrowsingContext* bc = window->GetBrowsingContext();
992 if (!bc || mFocusedBrowsingContextInContent != bc) {
993 return;
995 // Sync the window for a newly-created OOP iframe
996 // Set actionId to zero to signify that it should be ignored.
997 SetFocusedWindowInternal(window, 0, false);
1000 if (aNeedsFocus) {
1001 nsCOMPtr<nsPIDOMWindowOuter> currentWindow;
1002 RefPtr<Element> currentFocus = GetFocusedDescendant(
1003 window, eIncludeAllDescendants, getter_AddRefs(currentWindow));
1005 if (currentWindow) {
1006 Focus(currentWindow, currentFocus, 0, true, false, false, true,
1007 GenerateFocusActionId());
1009 } else {
1010 // Sometimes, an element in a window can be focused before the window is
1011 // visible, which would mean that the widget may not be properly focused.
1012 // When the window becomes visible, make sure the right widget is focused.
1013 EnsureCurrentWidgetFocused(CallerType::System);
1017 void nsFocusManager::WindowHidden(mozIDOMWindowProxy* aWindow,
1018 uint64_t aActionId) {
1019 // if there is no window or it is not the same or an ancestor of the
1020 // currently focused window, just return, as the current focus will not
1021 // be affected.
1023 if (!aWindow) {
1024 return;
1027 nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
1029 if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
1030 LOGFOCUS(("Window %p Hidden [Currently: %p %p] actionid: %" PRIu64,
1031 window.get(), mActiveWindow.get(), mFocusedWindow.get(),
1032 aActionId));
1033 nsAutoCString spec;
1034 Document* doc = window->GetExtantDoc();
1035 if (doc && doc->GetDocumentURI()) {
1036 LOGFOCUS((" Hide Window: %s",
1037 doc->GetDocumentURI()->GetSpecOrDefault().get()));
1040 if (mFocusedWindow) {
1041 doc = mFocusedWindow->GetExtantDoc();
1042 if (doc && doc->GetDocumentURI()) {
1043 LOGFOCUS((" Focused Window: %s",
1044 doc->GetDocumentURI()->GetSpecOrDefault().get()));
1048 if (mActiveWindow) {
1049 doc = mActiveWindow->GetExtantDoc();
1050 if (doc && doc->GetDocumentURI()) {
1051 LOGFOCUS((" Active Window: %s",
1052 doc->GetDocumentURI()->GetSpecOrDefault().get()));
1057 if (!IsSameOrAncestor(window, mFocusedWindow)) {
1058 return;
1061 // at this point, we know that the window being hidden is either the focused
1062 // window, or an ancestor of the focused window. Either way, the focus is no
1063 // longer valid, so it needs to be updated.
1065 const RefPtr<Element> oldFocusedElement = std::move(mFocusedElement);
1067 nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
1068 if (!focusedDocShell) {
1069 return;
1072 const RefPtr<PresShell> presShell = focusedDocShell->GetPresShell();
1074 if (oldFocusedElement && oldFocusedElement->IsInComposedDoc()) {
1075 NotifyFocusStateChange(oldFocusedElement, nullptr, 0, false, false);
1076 window->UpdateCommands(u"focus"_ns);
1078 if (presShell) {
1079 RefPtr<Document> composedDoc = oldFocusedElement->GetComposedDoc();
1080 SendFocusOrBlurEvent(eBlur, presShell, composedDoc, oldFocusedElement,
1081 false);
1085 const RefPtr<nsPresContext> focusedPresContext =
1086 presShell ? presShell->GetPresContext() : nullptr;
1087 IMEStateManager::OnChangeFocus(focusedPresContext, nullptr,
1088 GetFocusMoveActionCause(0));
1089 if (presShell) {
1090 SetCaretVisible(presShell, false, nullptr);
1093 // If a window is being "hidden" because its BrowsingContext is changing
1094 // remoteness, we don't want to handle docshell destruction by moving focus.
1095 // Instead, the focused browsing context should stay the way it is (so that
1096 // the newly "shown" window in the other process knows to take focus) and
1097 // we should just null out the process-local field.
1098 nsCOMPtr<nsIDocShell> docShellBeingHidden = window->GetDocShell();
1099 // Check if we're currently hiding a non-remote nsDocShell due to its
1100 // BrowsingContext navigating to become remote. Normally, when a focused
1101 // subframe is hidden, focus is moved to the frame element, but focus should
1102 // stay with the BrowsingContext when performing a process switch. We don't
1103 // need to consider process switches where the hiding docshell is already
1104 // remote (ie. GetEmbedderElement is nullptr), as shifting remoteness to the
1105 // frame element is handled elsewhere.
1106 if (docShellBeingHidden &&
1107 nsDocShell::Cast(docShellBeingHidden)->WillChangeProcess() &&
1108 docShellBeingHidden->GetBrowsingContext()->GetEmbedderElement()) {
1109 if (mFocusedWindow != window) {
1110 // The window being hidden is an ancestor of the focused window.
1111 #ifdef DEBUG
1112 BrowsingContext* ancestor = window->GetBrowsingContext();
1113 BrowsingContext* bc = mFocusedWindow->GetBrowsingContext();
1114 for (;;) {
1115 if (!bc) {
1116 MOZ_ASSERT(false, "Should have found ancestor");
1118 bc = bc->GetParent();
1119 if (ancestor == bc) {
1120 break;
1123 #endif
1124 // This call adjusts the focused browsing context and window.
1125 // The latter gets nulled out immediately below.
1126 SetFocusedWindowInternal(window, aActionId);
1128 mFocusedWindow = nullptr;
1129 window->SetFocusedElement(nullptr);
1130 return;
1133 // if the docshell being hidden is being destroyed, then we want to move
1134 // focus somewhere else. Call ClearFocus on the toplevel window, which
1135 // will have the effect of clearing the focus and moving the focused window
1136 // to the toplevel window. But if the window isn't being destroyed, we are
1137 // likely just loading a new document in it, so we want to maintain the
1138 // focused window so that the new document gets properly focused.
1139 bool beingDestroyed = !docShellBeingHidden;
1140 if (docShellBeingHidden) {
1141 docShellBeingHidden->IsBeingDestroyed(&beingDestroyed);
1143 if (beingDestroyed) {
1144 // There is usually no need to do anything if a toplevel window is going
1145 // away, as we assume that WindowLowered will be called. However, this may
1146 // not happen if nsIAppStartup::eForceQuit is used to quit, and can cause
1147 // a leak. So if the active window is being destroyed, call WindowLowered
1148 // directly.
1150 if (XRE_IsParentProcess()) {
1151 nsCOMPtr<nsPIDOMWindowOuter> activeWindow = mActiveWindow;
1152 if (activeWindow == mFocusedWindow || activeWindow == window) {
1153 WindowLowered(activeWindow, aActionId);
1154 } else {
1155 ClearFocus(activeWindow);
1157 } else {
1158 BrowsingContext* active = GetActiveBrowsingContext();
1159 if (active) {
1160 if (nsCOMPtr<nsPIDOMWindowOuter> activeWindow =
1161 active->GetDOMWindow()) {
1162 if ((mFocusedWindow &&
1163 mFocusedWindow->GetBrowsingContext() == active) ||
1164 (window->GetBrowsingContext() == active)) {
1165 WindowLowered(activeWindow, aActionId);
1166 } else {
1167 ClearFocus(activeWindow);
1169 } // else do nothing when an out-of-process iframe is torn down
1172 return;
1175 if (!XRE_IsParentProcess() &&
1176 mActiveBrowsingContextInContent ==
1177 docShellBeingHidden->GetBrowsingContext() &&
1178 mActiveBrowsingContextInContent->GetIsInBFCache()) {
1179 SetActiveBrowsingContextInContent(nullptr, aActionId);
1182 // if the window being hidden is an ancestor of the focused window, adjust
1183 // the focused window so that it points to the one being hidden. This
1184 // ensures that the focused window isn't in a chain of frames that doesn't
1185 // exist any more.
1186 if (window != mFocusedWindow) {
1187 nsCOMPtr<nsIDocShellTreeItem> dsti =
1188 mFocusedWindow ? mFocusedWindow->GetDocShell() : nullptr;
1189 if (dsti) {
1190 nsCOMPtr<nsIDocShellTreeItem> parentDsti;
1191 dsti->GetInProcessParent(getter_AddRefs(parentDsti));
1192 if (parentDsti) {
1193 if (nsCOMPtr<nsPIDOMWindowOuter> parentWindow =
1194 parentDsti->GetWindow()) {
1195 parentWindow->SetFocusedElement(nullptr);
1200 SetFocusedWindowInternal(window, aActionId);
1204 void nsFocusManager::FireDelayedEvents(Document* aDocument) {
1205 MOZ_ASSERT(aDocument);
1207 // fire any delayed focus and blur events in the same order that they were
1208 // added
1209 for (uint32_t i = 0; i < mDelayedBlurFocusEvents.Length(); i++) {
1210 if (mDelayedBlurFocusEvents[i].mDocument == aDocument) {
1211 if (!aDocument->GetInnerWindow() ||
1212 !aDocument->GetInnerWindow()->IsCurrentInnerWindow()) {
1213 // If the document was navigated away from or is defunct, don't bother
1214 // firing events on it. Note the symmetry between this condition and
1215 // the similar one in Document.cpp:FireOrClearDelayedEvents.
1216 mDelayedBlurFocusEvents.RemoveElementAt(i);
1217 --i;
1218 } else if (!aDocument->EventHandlingSuppressed()) {
1219 EventMessage message = mDelayedBlurFocusEvents[i].mEventMessage;
1220 nsCOMPtr<EventTarget> target = mDelayedBlurFocusEvents[i].mTarget;
1221 RefPtr<PresShell> presShell = mDelayedBlurFocusEvents[i].mPresShell;
1222 nsCOMPtr<EventTarget> relatedTarget =
1223 mDelayedBlurFocusEvents[i].mRelatedTarget;
1224 mDelayedBlurFocusEvents.RemoveElementAt(i);
1226 FireFocusOrBlurEvent(message, presShell, target, false, false,
1227 relatedTarget);
1228 --i;
1234 void nsFocusManager::WasNuked(nsPIDOMWindowOuter* aWindow) {
1235 MOZ_ASSERT(aWindow, "Expected non-null window.");
1236 MOZ_ASSERT(aWindow != mActiveWindow,
1237 "How come we're nuking a window that's still active?");
1238 if (aWindow == mFocusedWindow) {
1239 mFocusedWindow = nullptr;
1240 SetFocusedBrowsingContext(nullptr, GenerateFocusActionId());
1241 mFocusedElement = nullptr;
1245 nsFocusManager::BlurredElementInfo::BlurredElementInfo(Element& aElement)
1246 : mElement(aElement) {}
1248 nsFocusManager::BlurredElementInfo::~BlurredElementInfo() = default;
1250 // https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo
1251 static bool ShouldMatchFocusVisible(nsPIDOMWindowOuter* aWindow,
1252 const Element& aElement,
1253 int32_t aFocusFlags) {
1254 // If we were explicitly requested to show the ring, do it.
1255 if (aFocusFlags & nsIFocusManager::FLAG_SHOWRING) {
1256 return true;
1259 if (aFocusFlags & nsIFocusManager::FLAG_NOSHOWRING) {
1260 return false;
1263 if (aWindow->ShouldShowFocusRing()) {
1264 // The window decision also trumps any other heuristic.
1265 return true;
1268 // Any element which supports keyboard input (such as an input element, or any
1269 // other element which may trigger a virtual keyboard to be shown on focus if
1270 // a physical keyboard is not present) should always match :focus-visible when
1271 // focused.
1273 if (aElement.IsHTMLElement(nsGkAtoms::textarea) || aElement.IsEditable()) {
1274 return true;
1277 if (auto* input = HTMLInputElement::FromNode(aElement)) {
1278 if (input->IsSingleLineTextControl()) {
1279 return true;
1284 switch (nsFocusManager::GetFocusMoveActionCause(aFocusFlags)) {
1285 case InputContextAction::CAUSE_KEY:
1286 // If the user interacts with the page via the keyboard, the currently
1287 // focused element should match :focus-visible (i.e. keyboard usage may
1288 // change whether this pseudo-class matches even if it doesn't affect
1289 // :focus).
1290 return true;
1291 case InputContextAction::CAUSE_UNKNOWN:
1292 // We render outlines if the last "known" focus method was by key or there
1293 // was no previous known focus method, otherwise we don't.
1294 return aWindow->UnknownFocusMethodShouldShowOutline();
1295 case InputContextAction::CAUSE_MOUSE:
1296 case InputContextAction::CAUSE_TOUCH:
1297 case InputContextAction::CAUSE_LONGPRESS:
1298 // If the user interacts with the page via a pointing device, such that
1299 // the focus is moved to a new element which does not support user input,
1300 // the newly focused element should not match :focus-visible.
1301 return false;
1302 case InputContextAction::CAUSE_UNKNOWN_CHROME:
1303 case InputContextAction::CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT:
1304 case InputContextAction::CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT:
1305 // TODO(emilio): We could return some of these though, looking at
1306 // UserActivation. We may want to suppress focus rings for unknown /
1307 // programatic focus if the user is interacting with the page but not
1308 // during keyboard input, or such.
1309 MOZ_ASSERT_UNREACHABLE(
1310 "These don't get returned by GetFocusMoveActionCause");
1311 break;
1313 return false;
1316 /* static */
1317 void nsFocusManager::NotifyFocusStateChange(Element* aElement,
1318 Element* aElementToFocus,
1319 int32_t aFlags, bool aGettingFocus,
1320 bool aShouldShowFocusRing) {
1321 MOZ_ASSERT_IF(aElementToFocus, !aGettingFocus);
1322 nsIContent* commonAncestor = nullptr;
1323 if (aElementToFocus) {
1324 commonAncestor = nsContentUtils::GetCommonFlattenedTreeAncestor(
1325 aElement, aElementToFocus);
1328 if (aGettingFocus) {
1329 ElementState stateToAdd = ElementState::FOCUS;
1330 if (aShouldShowFocusRing) {
1331 stateToAdd |= ElementState::FOCUSRING;
1333 aElement->AddStates(stateToAdd);
1335 for (nsIContent* host = aElement->GetContainingShadowHost(); host;
1336 host = host->GetContainingShadowHost()) {
1337 host->AsElement()->AddStates(ElementState::FOCUS);
1339 } else {
1340 constexpr auto kStatesToRemove =
1341 ElementState::FOCUS | ElementState::FOCUSRING;
1342 aElement->RemoveStates(kStatesToRemove);
1343 for (nsIContent* host = aElement->GetContainingShadowHost(); host;
1344 host = host->GetContainingShadowHost()) {
1345 host->AsElement()->RemoveStates(kStatesToRemove);
1349 // Special case for <input type="checkbox"> and <input type="radio">.
1350 // The other browsers cancel active state when they gets lost focus, but
1351 // does not do it for the other elements such as <button> and <a href="...">.
1352 // Additionally, they may be activated with <label>, but they will get focus
1353 // at `click`, but activated at `mousedown`. Therefore, we need to cancel
1354 // active state at moving focus.
1355 if (RefPtr<nsPresContext> presContext =
1356 aElement->GetPresContext(Element::PresContextFor::eForComposedDoc)) {
1357 RefPtr<EventStateManager> esm = presContext->EventStateManager();
1358 auto* activeInputElement =
1359 HTMLInputElement::FromNodeOrNull(esm->GetActiveContent());
1360 if (activeInputElement &&
1361 (activeInputElement->ControlType() == FormControlType::InputCheckbox ||
1362 activeInputElement->ControlType() == FormControlType::InputRadio) &&
1363 !activeInputElement->State().HasState(ElementState::FOCUS)) {
1364 esm->SetContentState(nullptr, ElementState::ACTIVE);
1368 for (nsIContent* content = aElement; content && content != commonAncestor;
1369 content = content->GetFlattenedTreeParent()) {
1370 Element* element = Element::FromNode(content);
1371 if (!element) {
1372 continue;
1375 if (aGettingFocus) {
1376 if (element->State().HasState(ElementState::FOCUS_WITHIN)) {
1377 break;
1379 element->AddStates(ElementState::FOCUS_WITHIN);
1380 } else {
1381 element->RemoveStates(ElementState::FOCUS_WITHIN);
1386 // static
1387 void nsFocusManager::EnsureCurrentWidgetFocused(CallerType aCallerType) {
1388 if (!mFocusedWindow || sTestMode) return;
1390 // get the main child widget for the focused window and ensure that the
1391 // platform knows that this widget is focused.
1392 nsCOMPtr<nsIDocShell> docShell = mFocusedWindow->GetDocShell();
1393 if (!docShell) {
1394 return;
1396 RefPtr<PresShell> presShell = docShell->GetPresShell();
1397 if (!presShell) {
1398 return;
1400 nsViewManager* vm = presShell->GetViewManager();
1401 if (!vm) {
1402 return;
1404 nsCOMPtr<nsIWidget> widget = vm->GetRootWidget();
1405 if (!widget) {
1406 return;
1408 widget->SetFocus(nsIWidget::Raise::No, aCallerType);
1411 void nsFocusManager::ActivateOrDeactivate(nsPIDOMWindowOuter* aWindow,
1412 bool aActive) {
1413 MOZ_ASSERT(XRE_IsParentProcess());
1414 if (!aWindow) {
1415 return;
1418 if (BrowsingContext* bc = aWindow->GetBrowsingContext()) {
1419 MOZ_ASSERT(bc->IsTop());
1421 RefPtr<CanonicalBrowsingContext> chromeTop =
1422 bc->Canonical()->TopCrossChromeBoundary();
1423 MOZ_ASSERT(bc == chromeTop);
1425 chromeTop->SetIsActiveBrowserWindow(aActive);
1426 chromeTop->CallOnAllTopDescendants(
1427 [aActive](CanonicalBrowsingContext* aBrowsingContext) -> CallState {
1428 aBrowsingContext->SetIsActiveBrowserWindow(aActive);
1429 return CallState::Continue;
1433 if (aWindow->GetExtantDoc()) {
1434 nsContentUtils::DispatchEventOnlyToChrome(
1435 aWindow->GetExtantDoc(),
1436 nsGlobalWindowInner::Cast(aWindow->GetCurrentInnerWindow()),
1437 aActive ? u"activate"_ns : u"deactivate"_ns, CanBubble::eYes,
1438 Cancelable::eYes, nullptr);
1442 // Retrieves innerWindowId of the window of the last focused element to
1443 // log a warning to the website console.
1444 void LogWarningFullscreenWindowRaise(Element* aElement) {
1445 nsCOMPtr<nsFrameLoaderOwner> frameLoaderOwner(do_QueryInterface(aElement));
1446 NS_ENSURE_TRUE_VOID(frameLoaderOwner);
1448 RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader();
1449 NS_ENSURE_TRUE_VOID(frameLoaderOwner);
1451 RefPtr<BrowsingContext> browsingContext = frameLoader->GetBrowsingContext();
1452 NS_ENSURE_TRUE_VOID(browsingContext);
1454 WindowGlobalParent* windowGlobalParent =
1455 browsingContext->Canonical()->GetCurrentWindowGlobal();
1456 NS_ENSURE_TRUE_VOID(windowGlobalParent);
1458 // Log to console
1459 nsAutoString localizedMsg;
1460 nsTArray<nsString> params;
1461 nsresult rv = nsContentUtils::FormatLocalizedString(
1462 nsContentUtils::eDOM_PROPERTIES, "FullscreenExitWindowFocus", params,
1463 localizedMsg);
1465 NS_ENSURE_SUCCESS_VOID(rv);
1467 Unused << nsContentUtils::ReportToConsoleByWindowID(
1468 localizedMsg, nsIScriptError::warningFlag, "DOM"_ns,
1469 windowGlobalParent->InnerWindowId(),
1470 windowGlobalParent->GetDocumentURI());
1473 // Ensure that when an embedded popup with a noautofocus attribute
1474 // like a date picker is opened and focused, the parent page does not blur
1475 static bool IsEmeddededInNoautofocusPopup(BrowsingContext& aBc) {
1476 auto* embedder = aBc.GetEmbedderElement();
1477 if (!embedder) {
1478 return false;
1480 nsIFrame* f = embedder->GetPrimaryFrame();
1481 if (!f || !f->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
1482 return false;
1485 nsIFrame* menuPopup =
1486 nsLayoutUtils::GetClosestFrameOfType(f, LayoutFrameType::MenuPopup);
1487 MOZ_ASSERT(menuPopup, "NS_FRAME_IN_POPUP lied?");
1488 return static_cast<nsMenuPopupFrame*>(menuPopup)
1489 ->PopupElement()
1490 .GetXULBoolAttr(nsGkAtoms::noautofocus);
1493 Maybe<uint64_t> nsFocusManager::SetFocusInner(Element* aNewContent,
1494 int32_t aFlags,
1495 bool aFocusChanged,
1496 bool aAdjustWidget) {
1497 // if the element is not focusable, just return and leave the focus as is
1498 RefPtr<Element> elementToFocus =
1499 FlushAndCheckIfFocusable(aNewContent, aFlags);
1500 if (!elementToFocus) {
1501 return Nothing();
1504 const RefPtr<BrowsingContext> focusedBrowsingContext =
1505 GetFocusedBrowsingContext();
1507 // check if the element to focus is a frame (iframe) containing a child
1508 // document. Frames are never directly focused; instead focusing a frame
1509 // means focus what is inside the frame. To do this, the descendant content
1510 // within the frame is retrieved and that will be focused instead.
1511 nsCOMPtr<nsPIDOMWindowOuter> newWindow;
1512 nsCOMPtr<nsPIDOMWindowOuter> subWindow = GetContentWindow(elementToFocus);
1513 if (subWindow) {
1514 elementToFocus = GetFocusedDescendant(subWindow, eIncludeAllDescendants,
1515 getter_AddRefs(newWindow));
1517 // since a window is being refocused, clear aFocusChanged so that the
1518 // caret position isn't updated.
1519 aFocusChanged = false;
1522 // unless it was set above, retrieve the window for the element to focus
1523 if (!newWindow) {
1524 newWindow = GetCurrentWindow(elementToFocus);
1527 RefPtr<BrowsingContext> newBrowsingContext;
1528 if (newWindow) {
1529 newBrowsingContext = newWindow->GetBrowsingContext();
1532 // if the element is already focused, just return. Note that this happens
1533 // after the frame check above so that we compare the element that will be
1534 // focused rather than the frame it is in.
1535 if (!newWindow || (newBrowsingContext == GetFocusedBrowsingContext() &&
1536 elementToFocus == mFocusedElement)) {
1537 return Nothing();
1540 MOZ_ASSERT(newBrowsingContext);
1542 BrowsingContext* browsingContextToFocus = newBrowsingContext;
1543 if (RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(elementToFocus)) {
1544 // Only look at pre-existing browsing contexts. If this function is
1545 // called during reflow, calling GetBrowsingContext() could cause frame
1546 // loader initialization at a time when it isn't safe.
1547 if (BrowsingContext* bc = flo->GetExtantBrowsingContext()) {
1548 // If focus is already in the subtree rooted at bc, return early
1549 // to match the single-process focus semantics. Otherwise, we'd
1550 // blur and immediately refocus whatever is focused.
1551 BrowsingContext* walk = focusedBrowsingContext;
1552 while (walk) {
1553 if (walk == bc) {
1554 return Nothing();
1556 walk = walk->GetParent();
1558 browsingContextToFocus = bc;
1562 // don't allow focus to be placed in docshells or descendants of docshells
1563 // that are being destroyed. Also, ensure that the page hasn't been
1564 // unloaded. The prevents content from being refocused during an unload event.
1565 nsCOMPtr<nsIDocShell> newDocShell = newWindow->GetDocShell();
1566 nsCOMPtr<nsIDocShell> docShell = newDocShell;
1567 while (docShell) {
1568 bool inUnload;
1569 docShell->GetIsInUnload(&inUnload);
1570 if (inUnload) {
1571 return Nothing();
1574 bool beingDestroyed;
1575 docShell->IsBeingDestroyed(&beingDestroyed);
1576 if (beingDestroyed) {
1577 return Nothing();
1580 BrowsingContext* bc = docShell->GetBrowsingContext();
1582 nsCOMPtr<nsIDocShellTreeItem> parentDsti;
1583 docShell->GetInProcessParent(getter_AddRefs(parentDsti));
1584 docShell = do_QueryInterface(parentDsti);
1585 if (!docShell && !XRE_IsParentProcess()) {
1586 // We don't have an in-process parent, but let's see if we have
1587 // an in-process ancestor or if an out-of-process ancestor
1588 // is discarded.
1589 do {
1590 bc = bc->GetParent();
1591 if (bc && bc->IsDiscarded()) {
1592 return Nothing();
1594 } while (bc && !bc->IsInProcess());
1595 if (bc) {
1596 docShell = bc->GetDocShell();
1597 } else {
1598 docShell = nullptr;
1603 bool focusMovesToDifferentBC =
1604 (focusedBrowsingContext != browsingContextToFocus);
1606 if (focusedBrowsingContext && focusMovesToDifferentBC &&
1607 nsContentUtils::IsHandlingKeyBoardEvent() &&
1608 !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
1609 MOZ_ASSERT(browsingContextToFocus,
1610 "BrowsingContext to focus should be non-null.");
1612 nsIPrincipal* focusedPrincipal = nullptr;
1613 nsIPrincipal* newPrincipal = nullptr;
1615 if (XRE_IsParentProcess()) {
1616 if (WindowGlobalParent* focusedWindowGlobalParent =
1617 focusedBrowsingContext->Canonical()->GetCurrentWindowGlobal()) {
1618 focusedPrincipal = focusedWindowGlobalParent->DocumentPrincipal();
1621 if (WindowGlobalParent* newWindowGlobalParent =
1622 browsingContextToFocus->Canonical()->GetCurrentWindowGlobal()) {
1623 newPrincipal = newWindowGlobalParent->DocumentPrincipal();
1625 } else if (focusedBrowsingContext->IsInProcess() &&
1626 browsingContextToFocus->IsInProcess()) {
1627 nsCOMPtr<nsIScriptObjectPrincipal> focused =
1628 do_QueryInterface(focusedBrowsingContext->GetDOMWindow());
1629 nsCOMPtr<nsIScriptObjectPrincipal> newFocus =
1630 do_QueryInterface(browsingContextToFocus->GetDOMWindow());
1631 MOZ_ASSERT(focused && newFocus,
1632 "BrowsingContext should always have a window here.");
1633 focusedPrincipal = focused->GetPrincipal();
1634 newPrincipal = newFocus->GetPrincipal();
1637 if (!focusedPrincipal || !newPrincipal) {
1638 return Nothing();
1641 if (!focusedPrincipal->Subsumes(newPrincipal)) {
1642 NS_WARNING("Not allowed to focus the new window!");
1643 return Nothing();
1647 // to check if the new element is in the active window, compare the
1648 // new root docshell for the new element with the active window's docshell.
1649 RefPtr<BrowsingContext> newRootBrowsingContext = nullptr;
1650 bool isElementInActiveWindow = false;
1651 if (XRE_IsParentProcess()) {
1652 nsCOMPtr<nsPIDOMWindowOuter> newRootWindow = nullptr;
1653 nsCOMPtr<nsIDocShellTreeItem> dsti = newWindow->GetDocShell();
1654 if (dsti) {
1655 nsCOMPtr<nsIDocShellTreeItem> root;
1656 dsti->GetInProcessRootTreeItem(getter_AddRefs(root));
1657 newRootWindow = root ? root->GetWindow() : nullptr;
1659 isElementInActiveWindow =
1660 (mActiveWindow && newRootWindow == mActiveWindow);
1662 if (newRootWindow) {
1663 newRootBrowsingContext = newRootWindow->GetBrowsingContext();
1665 } else {
1666 // XXX This is wrong for `<iframe mozbrowser>` and for XUL
1667 // `<browser remote="true">`. See:
1668 // https://searchfox.org/mozilla-central/rev/8a63fc190b39ed6951abb4aef4a56487a43962bc/dom/base/nsFrameLoader.cpp#229-232
1669 newRootBrowsingContext = newBrowsingContext->Top();
1670 // to check if the new element is in the active window, compare the
1671 // new root docshell for the new element with the active window's docshell.
1672 isElementInActiveWindow =
1673 (GetActiveBrowsingContext() == newRootBrowsingContext);
1676 // Exit fullscreen if a website focuses another window
1677 if (StaticPrefs::full_screen_api_exit_on_windowRaise() &&
1678 !isElementInActiveWindow && (aFlags & FLAG_RAISE)) {
1679 if (XRE_IsParentProcess()) {
1680 if (Document* doc = mActiveWindow ? mActiveWindow->GetDoc() : nullptr) {
1681 Document::ClearPendingFullscreenRequests(doc);
1682 if (doc->GetFullscreenElement()) {
1683 LogWarningFullscreenWindowRaise(mFocusedElement);
1684 Document::AsyncExitFullscreen(doc);
1687 } else {
1688 BrowsingContext* activeBrowsingContext = GetActiveBrowsingContext();
1689 if (activeBrowsingContext) {
1690 nsIDocShell* shell = activeBrowsingContext->GetDocShell();
1691 if (shell) {
1692 if (Document* doc = shell->GetDocument()) {
1693 Document::ClearPendingFullscreenRequests(doc);
1694 if (doc->GetFullscreenElement()) {
1695 Document::AsyncExitFullscreen(doc);
1698 } else {
1699 mozilla::dom::ContentChild* contentChild =
1700 mozilla::dom::ContentChild::GetSingleton();
1701 MOZ_ASSERT(contentChild);
1702 contentChild->SendMaybeExitFullscreen(activeBrowsingContext);
1708 // if the FLAG_NOSWITCHFRAME flag is used, only allow the focus to be
1709 // shifted away from the current element if the new shell to focus is
1710 // the same or an ancestor shell of the currently focused shell.
1711 bool allowFrameSwitch = !(aFlags & FLAG_NOSWITCHFRAME) ||
1712 IsSameOrAncestor(newWindow, focusedBrowsingContext);
1714 // if the element is in the active window, frame switching is allowed and
1715 // the content is in a visible window, fire blur and focus events.
1716 bool sendFocusEvent =
1717 isElementInActiveWindow && allowFrameSwitch && IsWindowVisible(newWindow);
1719 // Don't allow to steal the focus from chrome nodes if the caller cannot
1720 // access them.
1721 if (sendFocusEvent && mFocusedElement &&
1722 mFocusedElement->OwnerDoc() != aNewContent->OwnerDoc() &&
1723 mFocusedElement->NodePrincipal()->IsSystemPrincipal() &&
1724 !nsContentUtils::LegacyIsCallerNativeCode() &&
1725 !nsContentUtils::CanCallerAccess(mFocusedElement)) {
1726 sendFocusEvent = false;
1729 LOGCONTENT("Shift Focus: %s", elementToFocus.get());
1730 LOGFOCUS((" Flags: %x Current Window: %p New Window: %p Current Element: %p",
1731 aFlags, mFocusedWindow.get(), newWindow.get(),
1732 mFocusedElement.get()));
1733 const uint64_t actionId = GenerateFocusActionId();
1734 LOGFOCUS(
1735 (" In Active Window: %d Moves to different BrowsingContext: %d "
1736 "SendFocus: %d actionid: %" PRIu64,
1737 isElementInActiveWindow, focusMovesToDifferentBC, sendFocusEvent,
1738 actionId));
1740 if (sendFocusEvent) {
1741 Maybe<BlurredElementInfo> blurredInfo;
1742 if (mFocusedElement) {
1743 blurredInfo.emplace(*mFocusedElement);
1745 // return if blurring fails or the focus changes during the blur
1746 if (focusedBrowsingContext) {
1747 // find the common ancestor of the currently focused window and the new
1748 // window. The ancestor will need to have its currently focused node
1749 // cleared once the document has been blurred. Otherwise, we'll be in a
1750 // state where a document is blurred yet the chain of windows above it
1751 // still points to that document.
1752 // For instance, in the following frame tree:
1753 // A
1754 // B C
1755 // D
1756 // D is focused and we want to focus C. Once D has been blurred, we need
1757 // to clear out the focus in A, otherwise A would still maintain that B
1758 // was focused, and B that D was focused.
1759 RefPtr<BrowsingContext> commonAncestor =
1760 focusMovesToDifferentBC
1761 ? GetCommonAncestor(newWindow, focusedBrowsingContext)
1762 : nullptr;
1764 const bool needToClearFocusedElement = [&] {
1765 if (focusedBrowsingContext->IsChrome()) {
1766 // Always reset focused element if focus is currently in chrome
1767 // window, unless we're moving focus to a popup.
1768 return !IsEmeddededInNoautofocusPopup(*browsingContextToFocus);
1770 if (focusedBrowsingContext->Top() != browsingContextToFocus->Top()) {
1771 // Only reset focused element if focus moves within the same top-level
1772 // content window.
1773 return false;
1775 // XXX for the case that we try to focus an
1776 // already-focused-remote-frame, we would still send blur and focus
1777 // IPC to it, but they will not generate blur or focus event, we don't
1778 // want to reset activeElement on the remote frame.
1779 return focusMovesToDifferentBC || focusedBrowsingContext->IsInProcess();
1780 }();
1782 const bool remainActive =
1783 focusMovesToDifferentBC &&
1784 IsEmeddededInNoautofocusPopup(*browsingContextToFocus);
1786 // TODO: MOZ_KnownLive is required due to bug 1770680
1787 if (!Blur(MOZ_KnownLive(needToClearFocusedElement
1788 ? focusedBrowsingContext.get()
1789 : nullptr),
1790 commonAncestor, focusMovesToDifferentBC, aAdjustWidget,
1791 remainActive, actionId, elementToFocus)) {
1792 return Some(actionId);
1796 Focus(newWindow, elementToFocus, aFlags, focusMovesToDifferentBC,
1797 aFocusChanged, false, aAdjustWidget, actionId, blurredInfo);
1798 } else {
1799 // otherwise, for inactive windows and when the caller cannot steal the
1800 // focus, update the node in the window, and raise the window if desired.
1801 if (allowFrameSwitch) {
1802 AdjustWindowFocus(newBrowsingContext, true, IsWindowVisible(newWindow),
1803 actionId);
1806 // set the focus node and method as needed
1807 uint32_t focusMethod =
1808 aFocusChanged ? aFlags & METHODANDRING_MASK
1809 : newWindow->GetFocusMethod() |
1810 (aFlags & (FLAG_SHOWRING | FLAG_NOSHOWRING));
1811 newWindow->SetFocusedElement(elementToFocus, focusMethod);
1812 if (aFocusChanged) {
1813 if (nsCOMPtr<nsIDocShell> docShell = newWindow->GetDocShell()) {
1814 RefPtr<PresShell> presShell = docShell->GetPresShell();
1815 if (presShell && presShell->DidInitialize()) {
1816 ScrollIntoView(presShell, elementToFocus, aFlags);
1821 // update the commands even when inactive so that the attributes for that
1822 // window are up to date.
1823 if (allowFrameSwitch) {
1824 newWindow->UpdateCommands(u"focus"_ns);
1827 if (aFlags & FLAG_RAISE) {
1828 if (newRootBrowsingContext) {
1829 if (XRE_IsParentProcess() || newRootBrowsingContext->IsInProcess()) {
1830 nsCOMPtr<nsPIDOMWindowOuter> outerWindow =
1831 newRootBrowsingContext->GetDOMWindow();
1832 RaiseWindow(outerWindow,
1833 aFlags & FLAG_NONSYSTEMCALLER ? CallerType::NonSystem
1834 : CallerType::System,
1835 actionId);
1836 } else {
1837 mozilla::dom::ContentChild* contentChild =
1838 mozilla::dom::ContentChild::GetSingleton();
1839 MOZ_ASSERT(contentChild);
1840 contentChild->SendRaiseWindow(newRootBrowsingContext,
1841 aFlags & FLAG_NONSYSTEMCALLER
1842 ? CallerType::NonSystem
1843 : CallerType::System,
1844 actionId);
1849 return Some(actionId);
1852 static already_AddRefed<BrowsingContext> GetParentIgnoreChromeBoundary(
1853 BrowsingContext* aBC) {
1854 // Chrome BrowsingContexts are only available in the parent process, so if
1855 // we're in a content process, we only worry about the context tree.
1856 if (XRE_IsParentProcess()) {
1857 return aBC->Canonical()->GetParentCrossChromeBoundary();
1859 return do_AddRef(aBC->GetParent());
1862 bool nsFocusManager::IsSameOrAncestor(BrowsingContext* aPossibleAncestor,
1863 BrowsingContext* aContext) const {
1864 if (!aPossibleAncestor) {
1865 return false;
1868 for (RefPtr<BrowsingContext> bc = aContext; bc;
1869 bc = GetParentIgnoreChromeBoundary(bc)) {
1870 if (bc == aPossibleAncestor) {
1871 return true;
1875 return false;
1878 bool nsFocusManager::IsSameOrAncestor(nsPIDOMWindowOuter* aPossibleAncestor,
1879 nsPIDOMWindowOuter* aWindow) const {
1880 if (aWindow && aPossibleAncestor) {
1881 return IsSameOrAncestor(aPossibleAncestor->GetBrowsingContext(),
1882 aWindow->GetBrowsingContext());
1884 return false;
1887 bool nsFocusManager::IsSameOrAncestor(nsPIDOMWindowOuter* aPossibleAncestor,
1888 BrowsingContext* aContext) const {
1889 if (aPossibleAncestor) {
1890 return IsSameOrAncestor(aPossibleAncestor->GetBrowsingContext(), aContext);
1892 return false;
1895 bool nsFocusManager::IsSameOrAncestor(BrowsingContext* aPossibleAncestor,
1896 nsPIDOMWindowOuter* aWindow) const {
1897 if (aWindow) {
1898 return IsSameOrAncestor(aPossibleAncestor, aWindow->GetBrowsingContext());
1900 return false;
1903 mozilla::dom::BrowsingContext* nsFocusManager::GetCommonAncestor(
1904 nsPIDOMWindowOuter* aWindow, mozilla::dom::BrowsingContext* aContext) {
1905 NS_ENSURE_TRUE(aWindow && aContext, nullptr);
1907 if (XRE_IsParentProcess()) {
1908 nsCOMPtr<nsIDocShellTreeItem> dsti1 = aWindow->GetDocShell();
1909 NS_ENSURE_TRUE(dsti1, nullptr);
1911 nsCOMPtr<nsIDocShellTreeItem> dsti2 = aContext->GetDocShell();
1912 NS_ENSURE_TRUE(dsti2, nullptr);
1914 AutoTArray<nsIDocShellTreeItem*, 30> parents1, parents2;
1915 do {
1916 parents1.AppendElement(dsti1);
1917 nsCOMPtr<nsIDocShellTreeItem> parentDsti1;
1918 dsti1->GetInProcessParent(getter_AddRefs(parentDsti1));
1919 dsti1.swap(parentDsti1);
1920 } while (dsti1);
1921 do {
1922 parents2.AppendElement(dsti2);
1923 nsCOMPtr<nsIDocShellTreeItem> parentDsti2;
1924 dsti2->GetInProcessParent(getter_AddRefs(parentDsti2));
1925 dsti2.swap(parentDsti2);
1926 } while (dsti2);
1928 uint32_t pos1 = parents1.Length();
1929 uint32_t pos2 = parents2.Length();
1930 nsIDocShellTreeItem* parent = nullptr;
1931 uint32_t len;
1932 for (len = std::min(pos1, pos2); len > 0; --len) {
1933 nsIDocShellTreeItem* child1 = parents1.ElementAt(--pos1);
1934 nsIDocShellTreeItem* child2 = parents2.ElementAt(--pos2);
1935 if (child1 != child2) {
1936 break;
1938 parent = child1;
1941 return parent ? parent->GetBrowsingContext() : nullptr;
1944 BrowsingContext* bc1 = aWindow->GetBrowsingContext();
1945 NS_ENSURE_TRUE(bc1, nullptr);
1947 BrowsingContext* bc2 = aContext;
1949 AutoTArray<BrowsingContext*, 30> parents1, parents2;
1950 do {
1951 parents1.AppendElement(bc1);
1952 bc1 = bc1->GetParent();
1953 } while (bc1);
1954 do {
1955 parents2.AppendElement(bc2);
1956 bc2 = bc2->GetParent();
1957 } while (bc2);
1959 uint32_t pos1 = parents1.Length();
1960 uint32_t pos2 = parents2.Length();
1961 BrowsingContext* parent = nullptr;
1962 uint32_t len;
1963 for (len = std::min(pos1, pos2); len > 0; --len) {
1964 BrowsingContext* child1 = parents1.ElementAt(--pos1);
1965 BrowsingContext* child2 = parents2.ElementAt(--pos2);
1966 if (child1 != child2) {
1967 break;
1969 parent = child1;
1972 return parent;
1975 bool nsFocusManager::AdjustInProcessWindowFocus(
1976 BrowsingContext* aBrowsingContext, bool aCheckPermission, bool aIsVisible,
1977 uint64_t aActionId) {
1978 if (ActionIdComparableAndLower(aActionId,
1979 mActionIdForFocusedBrowsingContextInContent)) {
1980 LOGFOCUS(
1981 ("Ignored an attempt to adjust an in-process BrowsingContext [%p] as "
1982 "focused from another process due to stale action id %" PRIu64 ".",
1983 aBrowsingContext, aActionId));
1984 return false;
1987 BrowsingContext* bc = aBrowsingContext;
1988 bool needToNotifyOtherProcess = false;
1989 while (bc) {
1990 // get the containing <iframe> or equivalent element so that it can be
1991 // focused below.
1992 nsCOMPtr<Element> frameElement = bc->GetEmbedderElement();
1993 BrowsingContext* parent = bc->GetParent();
1994 if (!parent && XRE_IsParentProcess()) {
1995 CanonicalBrowsingContext* canonical = bc->Canonical();
1996 RefPtr<WindowGlobalParent> embedder =
1997 canonical->GetEmbedderWindowGlobal();
1998 if (embedder) {
1999 parent = embedder->BrowsingContext();
2002 bc = parent;
2003 if (!bc) {
2004 break;
2006 if (!frameElement && XRE_IsContentProcess()) {
2007 needToNotifyOtherProcess = true;
2008 continue;
2011 nsCOMPtr<nsPIDOMWindowOuter> window = bc->GetDOMWindow();
2012 MOZ_ASSERT(window);
2013 // if the parent window is visible but the original window was not, then we
2014 // have likely moved up and out from a hidden tab to the browser window, or
2015 // a similar such arrangement. Stop adjusting the current nodes.
2016 if (IsWindowVisible(window) != aIsVisible) {
2017 break;
2020 // When aCheckPermission is true, we should check whether the caller can
2021 // access the window or not. If it cannot access, we should stop the
2022 // adjusting.
2023 if (aCheckPermission && !nsContentUtils::LegacyIsCallerNativeCode() &&
2024 !nsContentUtils::CanCallerAccess(window->GetCurrentInnerWindow())) {
2025 break;
2028 if (frameElement != window->GetFocusedElement()) {
2029 window->SetFocusedElement(frameElement);
2031 RefPtr<nsFrameLoaderOwner> loaderOwner = do_QueryObject(frameElement);
2032 MOZ_ASSERT(loaderOwner);
2033 RefPtr<nsFrameLoader> loader = loaderOwner->GetFrameLoader();
2034 if (loader && loader->IsRemoteFrame() &&
2035 GetFocusedBrowsingContext() == bc) {
2036 Blur(nullptr, nullptr, true, true, false, aActionId);
2040 return needToNotifyOtherProcess;
2043 void nsFocusManager::AdjustWindowFocus(BrowsingContext* aBrowsingContext,
2044 bool aCheckPermission, bool aIsVisible,
2045 uint64_t aActionId) {
2046 if (AdjustInProcessWindowFocus(aBrowsingContext, aCheckPermission, aIsVisible,
2047 aActionId)) {
2048 // Some ancestors of aBrowsingContext isn't in this process, so notify other
2049 // processes to adjust their focused element.
2050 mozilla::dom::ContentChild* contentChild =
2051 mozilla::dom::ContentChild::GetSingleton();
2052 MOZ_ASSERT(contentChild);
2053 contentChild->SendAdjustWindowFocus(aBrowsingContext, aIsVisible,
2054 aActionId);
2058 bool nsFocusManager::IsWindowVisible(nsPIDOMWindowOuter* aWindow) {
2059 if (!aWindow || aWindow->IsFrozen()) {
2060 return false;
2063 // Check if the inner window is frozen as well. This can happen when a focus
2064 // change occurs while restoring a previous page.
2065 nsPIDOMWindowInner* innerWindow = aWindow->GetCurrentInnerWindow();
2066 if (!innerWindow || innerWindow->IsFrozen()) {
2067 return false;
2070 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
2071 nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(docShell));
2072 if (!baseWin) {
2073 return false;
2076 bool visible = false;
2077 baseWin->GetVisibility(&visible);
2078 return visible;
2081 bool nsFocusManager::IsNonFocusableRoot(nsIContent* aContent) {
2082 MOZ_ASSERT(aContent, "aContent must not be NULL");
2083 MOZ_ASSERT(aContent->IsInComposedDoc(), "aContent must be in a document");
2085 // If the uncomposed document of aContent is in designMode, the root element
2086 // is not focusable.
2087 // NOTE: Most elements whose uncomposed document is in design mode are not
2088 // focusable, just the document is focusable. However, if it's in a
2089 // shadow tree, it may be focus able even if the shadow host is in
2090 // design mode.
2091 // Also, if aContent is not editable and it's not in designMode, it's not
2092 // focusable.
2093 // And in userfocusignored context nothing is focusable.
2094 Document* doc = aContent->GetComposedDoc();
2095 NS_ASSERTION(doc, "aContent must have current document");
2096 return aContent == doc->GetRootElement() &&
2097 (aContent->IsInDesignMode() || !aContent->IsEditable());
2100 Element* nsFocusManager::FlushAndCheckIfFocusable(Element* aElement,
2101 uint32_t aFlags) {
2102 if (!aElement) {
2103 return nullptr;
2106 nsCOMPtr<Document> doc = aElement->GetComposedDoc();
2107 // can't focus elements that are not in documents
2108 if (!doc) {
2109 LOGCONTENT("Cannot focus %s because content not in document", aElement)
2110 return nullptr;
2113 // Make sure that our frames are up to date while ensuring the presshell is
2114 // also initialized in case we come from a script calling focus() early.
2115 mEventHandlingNeedsFlush = false;
2116 doc->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames);
2118 PresShell* presShell = doc->GetPresShell();
2119 if (!presShell) {
2120 return nullptr;
2123 // If this is an iframe that doesn't have an in-process subdocument, it is
2124 // either an OOP iframe or an in-process iframe without lazy about:blank
2125 // creation having taken place. In the OOP case, iframe is always focusable.
2126 // In the in-process case, create the initial about:blank for in-process
2127 // BrowsingContexts in order to have the `GetSubDocumentFor` call after this
2128 // block return something.
2130 // TODO(emilio): This block can probably go after bug 543435 lands.
2131 if (RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(aElement)) {
2132 if (!aElement->IsXULElement()) {
2133 // Only look at pre-existing browsing contexts. If this function is
2134 // called during reflow, calling GetBrowsingContext() could cause frame
2135 // loader initialization at a time when it isn't safe.
2136 if (BrowsingContext* bc = flo->GetExtantBrowsingContext()) {
2137 // This call may create a contentViewer-created about:blank.
2138 // That's intentional, so we can move focus there.
2139 Unused << bc->GetDocument();
2144 return GetTheFocusableArea(aElement, aFlags);
2147 bool nsFocusManager::Blur(BrowsingContext* aBrowsingContextToClear,
2148 BrowsingContext* aAncestorBrowsingContextToFocus,
2149 bool aIsLeavingDocument, bool aAdjustWidget,
2150 bool aRemainActive, uint64_t aActionId,
2151 Element* aElementToFocus) {
2152 if (XRE_IsParentProcess()) {
2153 return BlurImpl(aBrowsingContextToClear, aAncestorBrowsingContextToFocus,
2154 aIsLeavingDocument, aAdjustWidget, aRemainActive,
2155 aElementToFocus, aActionId);
2157 mozilla::dom::ContentChild* contentChild =
2158 mozilla::dom::ContentChild::GetSingleton();
2159 MOZ_ASSERT(contentChild);
2160 bool windowToClearHandled = false;
2161 bool ancestorWindowToFocusHandled = false;
2163 RefPtr<BrowsingContext> focusedBrowsingContext = GetFocusedBrowsingContext();
2164 if (focusedBrowsingContext && focusedBrowsingContext->IsDiscarded()) {
2165 focusedBrowsingContext = nullptr;
2167 if (!focusedBrowsingContext) {
2168 mFocusedElement = nullptr;
2169 return true;
2171 if (aBrowsingContextToClear && aBrowsingContextToClear->IsDiscarded()) {
2172 aBrowsingContextToClear = nullptr;
2174 if (aAncestorBrowsingContextToFocus &&
2175 aAncestorBrowsingContextToFocus->IsDiscarded()) {
2176 aAncestorBrowsingContextToFocus = nullptr;
2178 // XXX should more early returns from BlurImpl be hoisted here to avoid
2179 // processing aBrowsingContextToClear and aAncestorBrowsingContextToFocus in
2180 // other processes when BlurImpl returns early in this process? Or should the
2181 // IPC messages for those be sent by BlurImpl itself, in which case they could
2182 // arrive late?
2183 if (focusedBrowsingContext->IsInProcess()) {
2184 if (aBrowsingContextToClear && !aBrowsingContextToClear->IsInProcess()) {
2185 MOZ_RELEASE_ASSERT(!(aAncestorBrowsingContextToFocus &&
2186 !aAncestorBrowsingContextToFocus->IsInProcess()),
2187 "Both aBrowsingContextToClear and "
2188 "aAncestorBrowsingContextToFocus are "
2189 "out-of-process.");
2190 contentChild->SendSetFocusedElement(aBrowsingContextToClear, false);
2192 if (aAncestorBrowsingContextToFocus &&
2193 !aAncestorBrowsingContextToFocus->IsInProcess()) {
2194 contentChild->SendSetFocusedElement(aAncestorBrowsingContextToFocus,
2195 true);
2197 return BlurImpl(aBrowsingContextToClear, aAncestorBrowsingContextToFocus,
2198 aIsLeavingDocument, aAdjustWidget, aRemainActive,
2199 aElementToFocus, aActionId);
2201 if (aBrowsingContextToClear && aBrowsingContextToClear->IsInProcess()) {
2202 nsPIDOMWindowOuter* windowToClear = aBrowsingContextToClear->GetDOMWindow();
2203 MOZ_ASSERT(windowToClear);
2204 windowToClear->SetFocusedElement(nullptr);
2205 windowToClearHandled = true;
2207 if (aAncestorBrowsingContextToFocus &&
2208 aAncestorBrowsingContextToFocus->IsInProcess()) {
2209 nsPIDOMWindowOuter* ancestorWindowToFocus =
2210 aAncestorBrowsingContextToFocus->GetDOMWindow();
2211 MOZ_ASSERT(ancestorWindowToFocus);
2212 ancestorWindowToFocus->SetFocusedElement(nullptr, 0, true);
2213 ancestorWindowToFocusHandled = true;
2215 // The expectation is that the blurring would eventually result in an IPC
2216 // message doing this anyway, but this doesn't happen if the focus is in OOP
2217 // iframe which won't try to bounce an IPC message to its parent frame.
2218 SetFocusedWindowInternal(nullptr, aActionId);
2219 contentChild->SendBlurToParent(
2220 focusedBrowsingContext, aBrowsingContextToClear,
2221 aAncestorBrowsingContextToFocus, aIsLeavingDocument, aAdjustWidget,
2222 windowToClearHandled, ancestorWindowToFocusHandled, aActionId);
2223 return true;
2226 void nsFocusManager::BlurFromOtherProcess(
2227 mozilla::dom::BrowsingContext* aFocusedBrowsingContext,
2228 mozilla::dom::BrowsingContext* aBrowsingContextToClear,
2229 mozilla::dom::BrowsingContext* aAncestorBrowsingContextToFocus,
2230 bool aIsLeavingDocument, bool aAdjustWidget, uint64_t aActionId) {
2231 if (aFocusedBrowsingContext != GetFocusedBrowsingContext()) {
2232 return;
2234 BlurImpl(aBrowsingContextToClear, aAncestorBrowsingContextToFocus,
2235 aIsLeavingDocument, aAdjustWidget, /* aRemainActive = */ false,
2236 nullptr, aActionId);
2239 bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear,
2240 BrowsingContext* aAncestorBrowsingContextToFocus,
2241 bool aIsLeavingDocument, bool aAdjustWidget,
2242 bool aRemainActive, Element* aElementToFocus,
2243 uint64_t aActionId) {
2244 LOGFOCUS(("<<Blur begin actionid: %" PRIu64 ">>", aActionId));
2246 // hold a reference to the focused content, which may be null
2247 RefPtr<Element> element = mFocusedElement;
2248 if (element) {
2249 if (!element->IsInComposedDoc()) {
2250 mFocusedElement = nullptr;
2251 return true;
2253 if (element == mFirstBlurEvent) {
2254 return true;
2258 RefPtr<BrowsingContext> focusedBrowsingContext = GetFocusedBrowsingContext();
2259 // hold a reference to the focused window
2260 nsCOMPtr<nsPIDOMWindowOuter> window;
2261 if (focusedBrowsingContext) {
2262 window = focusedBrowsingContext->GetDOMWindow();
2264 if (!window) {
2265 mFocusedElement = nullptr;
2266 return true;
2269 nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
2270 if (!docShell) {
2271 if (XRE_IsContentProcess() &&
2272 ActionIdComparableAndLower(
2273 aActionId, mActionIdForFocusedBrowsingContextInContent)) {
2274 // Unclear if this ever happens.
2275 LOGFOCUS(
2276 ("Ignored an attempt to null out focused BrowsingContext when "
2277 "docShell is null due to a stale action id %" PRIu64 ".",
2278 aActionId));
2279 return true;
2282 mFocusedWindow = nullptr;
2283 // Setting focused BrowsingContext to nullptr to avoid leaking in print
2284 // preview.
2285 SetFocusedBrowsingContext(nullptr, aActionId);
2286 mFocusedElement = nullptr;
2287 return true;
2290 // Keep a ref to presShell since dispatching the DOM event may cause
2291 // the document to be destroyed.
2292 RefPtr<PresShell> presShell = docShell->GetPresShell();
2293 if (!presShell) {
2294 if (XRE_IsContentProcess() &&
2295 ActionIdComparableAndLower(
2296 aActionId, mActionIdForFocusedBrowsingContextInContent)) {
2297 // Unclear if this ever happens.
2298 LOGFOCUS(
2299 ("Ignored an attempt to null out focused BrowsingContext when "
2300 "presShell is null due to a stale action id %" PRIu64 ".",
2301 aActionId));
2302 return true;
2304 mFocusedElement = nullptr;
2305 mFocusedWindow = nullptr;
2306 // Setting focused BrowsingContext to nullptr to avoid leaking in print
2307 // preview.
2308 SetFocusedBrowsingContext(nullptr, aActionId);
2309 return true;
2312 Maybe<AutoRestore<RefPtr<Element>>> ar;
2313 if (!mFirstBlurEvent) {
2314 ar.emplace(mFirstBlurEvent);
2315 mFirstBlurEvent = element;
2318 const RefPtr<nsPresContext> focusedPresContext =
2319 GetActiveBrowsingContext() ? presShell->GetPresContext() : nullptr;
2320 IMEStateManager::OnChangeFocus(focusedPresContext, nullptr,
2321 GetFocusMoveActionCause(0));
2323 // now adjust the actual focus, by clearing the fields in the focus manager
2324 // and in the window.
2325 mFocusedElement = nullptr;
2326 if (aBrowsingContextToClear) {
2327 nsPIDOMWindowOuter* windowToClear = aBrowsingContextToClear->GetDOMWindow();
2328 if (windowToClear) {
2329 windowToClear->SetFocusedElement(nullptr);
2333 LOGCONTENT("Element %s has been blurred", element.get());
2335 // Don't fire blur event on the root content which isn't editable.
2336 bool sendBlurEvent =
2337 element && element->IsInComposedDoc() && !IsNonFocusableRoot(element);
2338 if (element) {
2339 if (sendBlurEvent) {
2340 NotifyFocusStateChange(element, aElementToFocus, 0, false, false);
2343 if (!aRemainActive) {
2344 bool windowBeingLowered = !aBrowsingContextToClear &&
2345 !aAncestorBrowsingContextToFocus &&
2346 aIsLeavingDocument && aAdjustWidget;
2347 // If the object being blurred is a remote browser, deactivate remote
2348 // content
2349 if (BrowserParent* remote = BrowserParent::GetFrom(element)) {
2350 MOZ_ASSERT(XRE_IsParentProcess());
2351 // Let's deactivate all remote browsers.
2352 BrowsingContext* topLevelBrowsingContext = remote->GetBrowsingContext();
2353 topLevelBrowsingContext->PreOrderWalk([&](BrowsingContext* aContext) {
2354 if (WindowGlobalParent* windowGlobalParent =
2355 aContext->Canonical()->GetCurrentWindowGlobal()) {
2356 if (RefPtr<BrowserParent> browserParent =
2357 windowGlobalParent->GetBrowserParent()) {
2358 browserParent->Deactivate(windowBeingLowered, aActionId);
2359 LOGFOCUS(
2360 ("%s remote browser deactivated %p, %d, actionid: %" PRIu64,
2361 aContext == topLevelBrowsingContext ? "Top-level"
2362 : "OOP iframe",
2363 browserParent.get(), windowBeingLowered, aActionId));
2369 // Same as above but for out-of-process iframes
2370 if (BrowserBridgeChild* bbc = BrowserBridgeChild::GetFrom(element)) {
2371 bbc->Deactivate(windowBeingLowered, aActionId);
2372 LOGFOCUS(
2373 ("Out-of-process iframe deactivated %p, %d, actionid: %" PRIu64,
2374 bbc, windowBeingLowered, aActionId));
2379 bool result = true;
2380 if (sendBlurEvent) {
2381 // if there is an active window, update commands. If there isn't an active
2382 // window, then this was a blur caused by the active window being lowered,
2383 // so there is no need to update the commands
2384 if (GetActiveBrowsingContext()) {
2385 window->UpdateCommands(u"focus"_ns);
2388 SendFocusOrBlurEvent(eBlur, presShell, element->GetComposedDoc(), element,
2389 false, false, aElementToFocus);
2392 // if we are leaving the document or the window was lowered, make the caret
2393 // invisible.
2394 if (aIsLeavingDocument || !GetActiveBrowsingContext()) {
2395 SetCaretVisible(presShell, false, nullptr);
2398 RefPtr<AccessibleCaretEventHub> eventHub =
2399 presShell->GetAccessibleCaretEventHub();
2400 if (eventHub) {
2401 eventHub->NotifyBlur(aIsLeavingDocument || !GetActiveBrowsingContext());
2404 // at this point, it is expected that this window will be still be
2405 // focused, but the focused element will be null, as it was cleared before
2406 // the event. If this isn't the case, then something else was focused during
2407 // the blur event above and we should just return. However, if
2408 // aIsLeavingDocument is set, a new document is desired, so make sure to
2409 // blur the document and window.
2410 if (GetFocusedBrowsingContext() != window->GetBrowsingContext() ||
2411 (mFocusedElement != nullptr && !aIsLeavingDocument)) {
2412 result = false;
2413 } else if (aIsLeavingDocument) {
2414 window->TakeFocus(false, 0);
2416 // clear the focus so that the ancestor frame hierarchy is in the correct
2417 // state. Pass true because aAncestorBrowsingContextToFocus is thought to be
2418 // focused at this point.
2419 if (aAncestorBrowsingContextToFocus) {
2420 nsPIDOMWindowOuter* ancestorWindowToFocus =
2421 aAncestorBrowsingContextToFocus->GetDOMWindow();
2422 if (ancestorWindowToFocus) {
2423 ancestorWindowToFocus->SetFocusedElement(nullptr, 0, true);
2427 SetFocusedWindowInternal(nullptr, aActionId);
2428 mFocusedElement = nullptr;
2430 RefPtr<Document> doc = window->GetExtantDoc();
2431 if (doc) {
2432 SendFocusOrBlurEvent(eBlur, presShell, doc, doc, false);
2434 if (!GetFocusedBrowsingContext()) {
2435 nsCOMPtr<nsPIDOMWindowInner> innerWindow =
2436 window->GetCurrentInnerWindow();
2437 // MOZ_KnownLive due to bug 1506441
2438 SendFocusOrBlurEvent(
2439 eBlur, presShell, doc,
2440 MOZ_KnownLive(nsGlobalWindowInner::Cast(innerWindow)), false);
2443 // check if a different window was focused
2444 result = (!GetFocusedBrowsingContext() && GetActiveBrowsingContext());
2445 } else if (GetActiveBrowsingContext()) {
2446 // Otherwise, the blur of the element without blurring the document
2447 // occurred normally. Call UpdateCaret to redisplay the caret at the right
2448 // location within the document. This is needed to ensure that the caret
2449 // used for caret browsing is made visible again when an input field is
2450 // blurred.
2451 UpdateCaret(false, true, nullptr);
2454 return result;
2457 void nsFocusManager::ActivateRemoteFrameIfNeeded(Element& aElement,
2458 uint64_t aActionId) {
2459 if (BrowserParent* remote = BrowserParent::GetFrom(&aElement)) {
2460 remote->Activate(aActionId);
2461 LOGFOCUS(
2462 ("Remote browser activated %p, actionid: %" PRIu64, remote, aActionId));
2465 // Same as above but for out-of-process iframes
2466 if (BrowserBridgeChild* bbc = BrowserBridgeChild::GetFrom(&aElement)) {
2467 bbc->Activate(aActionId);
2468 LOGFOCUS(("Out-of-process iframe activated %p, actionid: %" PRIu64, bbc,
2469 aActionId));
2473 void nsFocusManager::Focus(
2474 nsPIDOMWindowOuter* aWindow, Element* aElement, uint32_t aFlags,
2475 bool aIsNewDocument, bool aFocusChanged, bool aWindowRaised,
2476 bool aAdjustWidget, uint64_t aActionId,
2477 const Maybe<BlurredElementInfo>& aBlurredElementInfo) {
2478 LOGFOCUS(("<<Focus begin actionid: %" PRIu64 ">>", aActionId));
2480 if (!aWindow) {
2481 return;
2484 if (aElement &&
2485 (aElement == mFirstFocusEvent || aElement == mFirstBlurEvent)) {
2486 return;
2489 // Keep a reference to the presShell since dispatching the DOM event may
2490 // cause the document to be destroyed.
2491 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
2492 if (!docShell) {
2493 return;
2496 const RefPtr<PresShell> presShell = docShell->GetPresShell();
2497 if (!presShell) {
2498 return;
2501 bool focusInOtherContentProcess = false;
2502 // Keep mochitest-browser-chrome harness happy by ignoring
2503 // focusInOtherContentProcess in the chrome process, because the harness
2504 // expects that.
2505 if (!XRE_IsParentProcess()) {
2506 if (RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(aElement)) {
2507 // Only look at pre-existing browsing contexts. If this function is
2508 // called during reflow, calling GetBrowsingContext() could cause frame
2509 // loader initialization at a time when it isn't safe.
2510 if (BrowsingContext* bc = flo->GetExtantBrowsingContext()) {
2511 focusInOtherContentProcess = !bc->IsInProcess();
2515 if (ActionIdComparableAndLower(
2516 aActionId, mActionIdForFocusedBrowsingContextInContent)) {
2517 // Unclear if this ever happens.
2518 LOGFOCUS(
2519 ("Ignored an attempt to focus an element due to stale action id "
2520 "%" PRIu64 ".",
2521 aActionId));
2522 return;
2526 // If the focus actually changed, set the focus method (mouse, keyboard, etc).
2527 // Otherwise, just get the current focus method and use that. This ensures
2528 // that the method is set during the document and window focus events.
2529 uint32_t focusMethod = aFocusChanged
2530 ? aFlags & METHODANDRING_MASK
2531 : aWindow->GetFocusMethod() |
2532 (aFlags & (FLAG_SHOWRING | FLAG_NOSHOWRING));
2534 if (!IsWindowVisible(aWindow)) {
2535 // if the window isn't visible, for instance because it is a hidden tab,
2536 // update the current focus and scroll it into view but don't do anything
2537 // else
2538 if (RefPtr elementToFocus = FlushAndCheckIfFocusable(aElement, aFlags)) {
2539 aWindow->SetFocusedElement(elementToFocus, focusMethod);
2540 if (aFocusChanged) {
2541 ScrollIntoView(presShell, elementToFocus, aFlags);
2544 return;
2547 Maybe<AutoRestore<RefPtr<Element>>> ar;
2548 if (!mFirstFocusEvent) {
2549 ar.emplace(mFirstFocusEvent);
2550 mFirstFocusEvent = aElement;
2553 LOGCONTENT("Element %s has been focused", aElement);
2555 if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
2556 Document* docm = aWindow->GetExtantDoc();
2557 if (docm) {
2558 LOGCONTENT(" from %s", docm->GetRootElement());
2560 LOGFOCUS(
2561 (" [Newdoc: %d FocusChanged: %d Raised: %d Flags: %x actionid: %" PRIu64
2562 "]",
2563 aIsNewDocument, aFocusChanged, aWindowRaised, aFlags, aActionId));
2566 if (aIsNewDocument) {
2567 // if this is a new document, update the parent chain of frames so that
2568 // focus can be traversed from the top level down to the newly focused
2569 // window.
2570 RefPtr<BrowsingContext> bc = aWindow->GetBrowsingContext();
2571 AdjustWindowFocus(bc, false, IsWindowVisible(aWindow), aActionId);
2574 // indicate that the window has taken focus.
2575 if (aWindow->TakeFocus(true, focusMethod)) {
2576 aIsNewDocument = true;
2579 SetFocusedWindowInternal(aWindow, aActionId);
2581 if (aAdjustWidget && !sTestMode) {
2582 if (nsViewManager* vm = presShell->GetViewManager()) {
2583 nsCOMPtr<nsIWidget> widget = vm->GetRootWidget();
2584 if (widget)
2585 widget->SetFocus(nsIWidget::Raise::No, aFlags & FLAG_NONSYSTEMCALLER
2586 ? CallerType::NonSystem
2587 : CallerType::System);
2591 // if switching to a new document, first fire the focus event on the
2592 // document and then the window.
2593 if (aIsNewDocument) {
2594 RefPtr<Document> doc = aWindow->GetExtantDoc();
2595 // The focus change should be notified to IMEStateManager from here if:
2596 // * the focused element is in design mode or
2597 // * nobody gets focus and the document is in design mode
2598 // since any element whose uncomposed document is in design mode won't
2599 // receive focus event.
2600 if (doc && ((aElement && aElement->IsInDesignMode()) ||
2601 (!aElement && doc->IsInDesignMode()))) {
2602 RefPtr<nsPresContext> presContext = presShell->GetPresContext();
2603 IMEStateManager::OnChangeFocus(presContext, nullptr,
2604 GetFocusMoveActionCause(aFlags));
2606 if (doc && !focusInOtherContentProcess) {
2607 SendFocusOrBlurEvent(eFocus, presShell, doc, doc, aWindowRaised);
2609 if (GetFocusedBrowsingContext() == aWindow->GetBrowsingContext() &&
2610 !mFocusedElement && !focusInOtherContentProcess) {
2611 nsCOMPtr<nsPIDOMWindowInner> innerWindow =
2612 aWindow->GetCurrentInnerWindow();
2613 // MOZ_KnownLive due to bug 1506441
2614 SendFocusOrBlurEvent(
2615 eFocus, presShell, doc,
2616 MOZ_KnownLive(nsGlobalWindowInner::Cast(innerWindow)), aWindowRaised);
2620 // check to ensure that the element is still focusable, and that nothing
2621 // else was focused during the events above.
2622 // Note that the focusing element may have already been moved to another
2623 // document/window. In that case, we should stop setting focus to it
2624 // because setting focus to the new window would cause redirecting focus
2625 // again and again.
2626 RefPtr elementToFocus =
2627 aElement && aElement->IsInComposedDoc() &&
2628 aElement->GetComposedDoc() == aWindow->GetExtantDoc()
2629 ? FlushAndCheckIfFocusable(aElement, aFlags)
2630 : nullptr;
2631 if (elementToFocus && !mFocusedElement &&
2632 GetFocusedBrowsingContext() == aWindow->GetBrowsingContext()) {
2633 mFocusedElement = elementToFocus;
2635 nsIContent* focusedNode = aWindow->GetFocusedElement();
2636 const bool sendFocusEvent = elementToFocus->IsInComposedDoc() &&
2637 !IsNonFocusableRoot(elementToFocus);
2638 const bool isRefocus = focusedNode && focusedNode == elementToFocus;
2639 const bool shouldShowFocusRing =
2640 sendFocusEvent &&
2641 ShouldMatchFocusVisible(aWindow, *elementToFocus, aFlags);
2643 aWindow->SetFocusedElement(elementToFocus, focusMethod, false);
2645 const RefPtr<nsPresContext> presContext = presShell->GetPresContext();
2646 if (sendFocusEvent) {
2647 NotifyFocusStateChange(elementToFocus, nullptr, aFlags,
2648 /* aGettingFocus = */ true, shouldShowFocusRing);
2650 // If this is a remote browser, focus its widget and activate remote
2651 // content. Note that we might no longer be in the same document,
2652 // due to the events we fired above when aIsNewDocument.
2653 if (presShell->GetDocument() == elementToFocus->GetComposedDoc()) {
2654 ActivateRemoteFrameIfNeeded(*elementToFocus, aActionId);
2657 IMEStateManager::OnChangeFocus(presContext, elementToFocus,
2658 GetFocusMoveActionCause(aFlags));
2660 // as long as this focus wasn't because a window was raised, update the
2661 // commands
2662 // XXXndeakin P2 someone could adjust the focus during the update
2663 if (!aWindowRaised) {
2664 aWindow->UpdateCommands(u"focus"_ns);
2667 // If the focused element changed, scroll it into view
2668 if (aFocusChanged) {
2669 ScrollIntoView(presShell, elementToFocus, aFlags);
2672 if (!focusInOtherContentProcess) {
2673 RefPtr<Document> composedDocument = elementToFocus->GetComposedDoc();
2674 RefPtr<Element> relatedTargetElement =
2675 aBlurredElementInfo ? aBlurredElementInfo->mElement.get() : nullptr;
2676 SendFocusOrBlurEvent(eFocus, presShell, composedDocument,
2677 elementToFocus, aWindowRaised, isRefocus,
2678 relatedTargetElement);
2680 } else {
2681 // We should notify IMEStateManager of actual focused element even if it
2682 // won't get focus event because the other IMEStateManager users do not
2683 // want to depend on this check, but IMEStateManager wants to verify
2684 // passed focused element for avoidng to overrride nested calls.
2685 IMEStateManager::OnChangeFocus(presContext, elementToFocus,
2686 GetFocusMoveActionCause(aFlags));
2687 if (!aWindowRaised) {
2688 aWindow->UpdateCommands(u"focus"_ns);
2690 if (aFocusChanged) {
2691 // If the focused element changed, scroll it into view
2692 ScrollIntoView(presShell, elementToFocus, aFlags);
2695 } else {
2696 if (!mFocusedElement && mFocusedWindow == aWindow) {
2697 // When there is no focused element, IMEStateManager needs to adjust IME
2698 // enabled state with the document.
2699 RefPtr<nsPresContext> presContext = presShell->GetPresContext();
2700 IMEStateManager::OnChangeFocus(presContext, nullptr,
2701 GetFocusMoveActionCause(aFlags));
2704 if (!aWindowRaised) {
2705 aWindow->UpdateCommands(u"focus"_ns);
2709 // update the caret visibility and position to match the newly focused
2710 // element. However, don't update the position if this was a focus due to a
2711 // mouse click as the selection code would already have moved the caret as
2712 // needed. If this is a different document than was focused before, also
2713 // update the caret's visibility. If this is the same document, the caret
2714 // visibility should be the same as before so there is no need to update it.
2715 if (mFocusedElement == elementToFocus) {
2716 RefPtr<Element> focusedElement = mFocusedElement;
2717 UpdateCaret(aFocusChanged && !(aFlags & FLAG_BYMOUSE), aIsNewDocument,
2718 focusedElement);
2722 class FocusBlurEvent : public Runnable {
2723 public:
2724 FocusBlurEvent(EventTarget* aTarget, EventMessage aEventMessage,
2725 nsPresContext* aContext, bool aWindowRaised, bool aIsRefocus,
2726 EventTarget* aRelatedTarget)
2727 : mozilla::Runnable("FocusBlurEvent"),
2728 mTarget(aTarget),
2729 mContext(aContext),
2730 mEventMessage(aEventMessage),
2731 mWindowRaised(aWindowRaised),
2732 mIsRefocus(aIsRefocus),
2733 mRelatedTarget(aRelatedTarget) {}
2735 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
2736 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
2737 InternalFocusEvent event(true, mEventMessage);
2738 event.mFlags.mBubbles = false;
2739 event.mFlags.mCancelable = false;
2740 event.mFromRaise = mWindowRaised;
2741 event.mIsRefocus = mIsRefocus;
2742 event.mRelatedTarget = mRelatedTarget;
2743 return EventDispatcher::Dispatch(mTarget, mContext, &event);
2746 const nsCOMPtr<EventTarget> mTarget;
2747 const RefPtr<nsPresContext> mContext;
2748 EventMessage mEventMessage;
2749 bool mWindowRaised;
2750 bool mIsRefocus;
2751 nsCOMPtr<EventTarget> mRelatedTarget;
2754 class FocusInOutEvent : public Runnable {
2755 public:
2756 FocusInOutEvent(EventTarget* aTarget, EventMessage aEventMessage,
2757 nsPresContext* aContext,
2758 nsPIDOMWindowOuter* aOriginalFocusedWindow,
2759 nsIContent* aOriginalFocusedContent,
2760 EventTarget* aRelatedTarget)
2761 : mozilla::Runnable("FocusInOutEvent"),
2762 mTarget(aTarget),
2763 mContext(aContext),
2764 mEventMessage(aEventMessage),
2765 mOriginalFocusedWindow(aOriginalFocusedWindow),
2766 mOriginalFocusedContent(aOriginalFocusedContent),
2767 mRelatedTarget(aRelatedTarget) {}
2769 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
2770 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
2771 nsCOMPtr<nsIContent> originalWindowFocus =
2772 mOriginalFocusedWindow ? mOriginalFocusedWindow->GetFocusedElement()
2773 : nullptr;
2774 // Blink does not check that focus is the same after blur, but WebKit does.
2775 // Opt to follow Blink's behavior (see bug 687787).
2776 if (mEventMessage == eFocusOut ||
2777 originalWindowFocus == mOriginalFocusedContent) {
2778 InternalFocusEvent event(true, mEventMessage);
2779 event.mFlags.mBubbles = true;
2780 event.mFlags.mCancelable = false;
2781 event.mRelatedTarget = mRelatedTarget;
2782 return EventDispatcher::Dispatch(mTarget, mContext, &event);
2784 return NS_OK;
2787 const nsCOMPtr<EventTarget> mTarget;
2788 const RefPtr<nsPresContext> mContext;
2789 EventMessage mEventMessage;
2790 nsCOMPtr<nsPIDOMWindowOuter> mOriginalFocusedWindow;
2791 nsCOMPtr<nsIContent> mOriginalFocusedContent;
2792 nsCOMPtr<EventTarget> mRelatedTarget;
2795 static Document* GetDocumentHelper(EventTarget* aTarget) {
2796 if (!aTarget) {
2797 return nullptr;
2799 if (const nsINode* node = nsINode::FromEventTarget(aTarget)) {
2800 return node->OwnerDoc();
2802 nsPIDOMWindowInner* win = nsPIDOMWindowInner::FromEventTarget(aTarget);
2803 return win ? win->GetExtantDoc() : nullptr;
2806 void nsFocusManager::FireFocusInOrOutEvent(
2807 EventMessage aEventMessage, PresShell* aPresShell, EventTarget* aTarget,
2808 nsPIDOMWindowOuter* aCurrentFocusedWindow,
2809 nsIContent* aCurrentFocusedContent, EventTarget* aRelatedTarget) {
2810 NS_ASSERTION(aEventMessage == eFocusIn || aEventMessage == eFocusOut,
2811 "Wrong event type for FireFocusInOrOutEvent");
2813 nsContentUtils::AddScriptRunner(new FocusInOutEvent(
2814 aTarget, aEventMessage, aPresShell->GetPresContext(),
2815 aCurrentFocusedWindow, aCurrentFocusedContent, aRelatedTarget));
2818 void nsFocusManager::SendFocusOrBlurEvent(EventMessage aEventMessage,
2819 PresShell* aPresShell,
2820 Document* aDocument,
2821 EventTarget* aTarget,
2822 bool aWindowRaised, bool aIsRefocus,
2823 EventTarget* aRelatedTarget) {
2824 NS_ASSERTION(aEventMessage == eFocus || aEventMessage == eBlur,
2825 "Wrong event type for SendFocusOrBlurEvent");
2827 nsCOMPtr<Document> eventTargetDoc = GetDocumentHelper(aTarget);
2828 nsCOMPtr<Document> relatedTargetDoc = GetDocumentHelper(aRelatedTarget);
2830 // set aRelatedTarget to null if it's not in the same document as aTarget
2831 if (eventTargetDoc != relatedTargetDoc) {
2832 aRelatedTarget = nullptr;
2835 if (aDocument && aDocument->EventHandlingSuppressed()) {
2836 // if this event was already queued, remove it and append it to the end
2837 mDelayedBlurFocusEvents.RemoveElementsBy([&](const auto& event) {
2838 return event.mEventMessage == aEventMessage &&
2839 event.mPresShell == aPresShell && event.mDocument == aDocument &&
2840 event.mTarget == aTarget && event.mRelatedTarget == aRelatedTarget;
2843 mDelayedBlurFocusEvents.EmplaceBack(aEventMessage, aPresShell, aDocument,
2844 aTarget, aRelatedTarget);
2845 return;
2848 // If mDelayedBlurFocusEvents queue is not empty, check if there are events
2849 // that belongs to this doc, if yes, fire them first.
2850 if (aDocument && !aDocument->EventHandlingSuppressed() &&
2851 mDelayedBlurFocusEvents.Length()) {
2852 FireDelayedEvents(aDocument);
2855 FireFocusOrBlurEvent(aEventMessage, aPresShell, aTarget, aWindowRaised,
2856 aIsRefocus, aRelatedTarget);
2859 void nsFocusManager::FireFocusOrBlurEvent(EventMessage aEventMessage,
2860 PresShell* aPresShell,
2861 EventTarget* aTarget,
2862 bool aWindowRaised, bool aIsRefocus,
2863 EventTarget* aRelatedTarget) {
2864 nsCOMPtr<nsPIDOMWindowOuter> currentWindow = mFocusedWindow;
2865 nsCOMPtr<nsPIDOMWindowInner> targetWindow = do_QueryInterface(aTarget);
2866 nsCOMPtr<Document> targetDocument = do_QueryInterface(aTarget);
2867 nsCOMPtr<nsIContent> currentFocusedContent =
2868 currentWindow ? currentWindow->GetFocusedElement() : nullptr;
2870 #ifdef ACCESSIBILITY
2871 nsAccessibilityService* accService = GetAccService();
2872 if (accService) {
2873 if (aEventMessage == eFocus) {
2874 accService->NotifyOfDOMFocus(aTarget);
2875 } else {
2876 accService->NotifyOfDOMBlur(aTarget);
2879 #endif
2881 aPresShell->ScheduleContentRelevancyUpdate(
2882 ContentRelevancyReason::FocusInSubtree);
2884 nsContentUtils::AddScriptRunner(
2885 new FocusBlurEvent(aTarget, aEventMessage, aPresShell->GetPresContext(),
2886 aWindowRaised, aIsRefocus, aRelatedTarget));
2888 // Check that the target is not a window or document before firing
2889 // focusin/focusout. Other browsers do not fire focusin/focusout on window,
2890 // despite being required in the spec, so follow their behavior.
2892 // As for document, we should not even fire focus/blur, but until then, we
2893 // need this check. targetDocument should be removed once bug 1228802 is
2894 // resolved.
2895 if (!targetWindow && !targetDocument) {
2896 EventMessage focusInOrOutMessage =
2897 aEventMessage == eFocus ? eFocusIn : eFocusOut;
2898 FireFocusInOrOutEvent(focusInOrOutMessage, aPresShell, aTarget,
2899 currentWindow, currentFocusedContent, aRelatedTarget);
2903 void nsFocusManager::ScrollIntoView(PresShell* aPresShell, nsIContent* aContent,
2904 uint32_t aFlags) {
2905 if (aFlags & FLAG_NOSCROLL) {
2906 return;
2909 // If the noscroll flag isn't set, scroll the newly focused element into view.
2910 const ScrollAxis axis(WhereToScroll::Center, WhenToScroll::IfNotVisible);
2911 aPresShell->ScrollContentIntoView(aContent, axis, axis,
2912 ScrollFlags::ScrollOverflowHidden);
2913 // Scroll the input / textarea selection into view, unless focused with the
2914 // mouse, see bug 572649.
2915 if (aFlags & FLAG_BYMOUSE) {
2916 return;
2918 // ScrollContentIntoView flushes layout, so no need to flush again here.
2919 if (nsTextControlFrame* tf = do_QueryFrame(aContent->GetPrimaryFrame())) {
2920 tf->ScrollSelectionIntoViewAsync(nsTextControlFrame::ScrollAncestors::Yes);
2924 void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow,
2925 CallerType aCallerType, uint64_t aActionId) {
2926 // don't raise windows that are already raised or are in the process of
2927 // being lowered
2929 if (!aWindow || aWindow == mWindowBeingLowered) {
2930 return;
2933 if (XRE_IsParentProcess()) {
2934 if (aWindow == mActiveWindow) {
2935 return;
2937 } else {
2938 BrowsingContext* bc = aWindow->GetBrowsingContext();
2939 // TODO: Deeper OOP frame hierarchies are
2940 // https://bugzilla.mozilla.org/show_bug.cgi?id=1661227
2941 if (bc == GetActiveBrowsingContext()) {
2942 return;
2944 if (bc == GetFocusedBrowsingContext()) {
2945 return;
2949 if (sTestMode) {
2950 // In test mode, emulate raising the window. WindowRaised takes
2951 // care of lowering the present active window. This happens in
2952 // a separate runnable to avoid touching multiple windows in
2953 // the current runnable.
2955 nsCOMPtr<nsPIDOMWindowOuter> window(aWindow);
2956 RefPtr<nsFocusManager> self(this);
2957 NS_DispatchToCurrentThread(NS_NewRunnableFunction(
2958 "nsFocusManager::RaiseWindow",
2959 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1770093)
2960 [self, window]() MOZ_CAN_RUN_SCRIPT_BOUNDARY -> void {
2961 self->WindowRaised(window, GenerateFocusActionId());
2962 }));
2963 return;
2966 if (XRE_IsContentProcess()) {
2967 BrowsingContext* bc = aWindow->GetBrowsingContext();
2968 if (!bc->IsTop()) {
2969 // Assume the raise below will succeed and run the raising synchronously
2970 // in this process to make the focus event that is observable in this
2971 // process fire in the right order relative to mouseup when we are here
2972 // thanks to a mousedown.
2973 WindowRaised(aWindow, aActionId);
2977 #if defined(XP_WIN)
2978 // Windows would rather we focus the child widget, otherwise, the toplevel
2979 // widget will always end up being focused. Fortunately, focusing the child
2980 // widget will also have the effect of raising the window this widget is in.
2981 // But on other platforms, we can just focus the toplevel widget to raise
2982 // the window.
2983 nsCOMPtr<nsPIDOMWindowOuter> childWindow;
2984 GetFocusedDescendant(aWindow, eIncludeAllDescendants,
2985 getter_AddRefs(childWindow));
2986 if (!childWindow) {
2987 childWindow = aWindow;
2990 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
2991 if (!docShell) {
2992 return;
2995 PresShell* presShell = docShell->GetPresShell();
2996 if (!presShell) {
2997 return;
3000 if (nsViewManager* vm = presShell->GetViewManager()) {
3001 nsCOMPtr<nsIWidget> widget = vm->GetRootWidget();
3002 if (widget) {
3003 widget->SetFocus(nsIWidget::Raise::Yes, aCallerType);
3006 #else
3007 nsCOMPtr<nsIBaseWindow> treeOwnerAsWin =
3008 do_QueryInterface(aWindow->GetDocShell());
3009 if (treeOwnerAsWin) {
3010 nsCOMPtr<nsIWidget> widget;
3011 treeOwnerAsWin->GetMainWidget(getter_AddRefs(widget));
3012 if (widget) {
3013 widget->SetFocus(nsIWidget::Raise::Yes, aCallerType);
3016 #endif
3019 void nsFocusManager::UpdateCaretForCaretBrowsingMode() {
3020 RefPtr<Element> focusedElement = mFocusedElement;
3021 UpdateCaret(false, true, focusedElement);
3024 void nsFocusManager::UpdateCaret(bool aMoveCaretToFocus, bool aUpdateVisibility,
3025 nsIContent* aContent) {
3026 LOGFOCUS(("Update Caret: %d %d", aMoveCaretToFocus, aUpdateVisibility));
3028 if (!mFocusedWindow) {
3029 return;
3032 // this is called when a document is focused or when the caretbrowsing
3033 // preference is changed
3034 nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
3035 if (!focusedDocShell) {
3036 return;
3039 if (focusedDocShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
3040 return; // Never browse with caret in chrome
3043 bool browseWithCaret = Preferences::GetBool("accessibility.browsewithcaret");
3045 const RefPtr<PresShell> presShell = focusedDocShell->GetPresShell();
3046 if (!presShell) {
3047 return;
3050 // If this is an editable document which isn't contentEditable, or a
3051 // contentEditable document and the node to focus is contentEditable,
3052 // return, so that we don't mess with caret visibility.
3053 bool isEditable = false;
3054 focusedDocShell->GetEditable(&isEditable);
3056 if (isEditable) {
3057 Document* doc = presShell->GetDocument();
3059 bool isContentEditableDoc =
3060 doc &&
3061 doc->GetEditingState() == Document::EditingState::eContentEditable;
3063 bool isFocusEditable = aContent && aContent->HasFlag(NODE_IS_EDITABLE);
3064 if (!isContentEditableDoc || isFocusEditable) {
3065 return;
3069 if (!isEditable && aMoveCaretToFocus) {
3070 MoveCaretToFocus(presShell, aContent);
3073 // The above MoveCaretToFocus call may run scripts which
3074 // may clear mFocusWindow
3075 if (!mFocusedWindow) {
3076 return;
3079 if (!aUpdateVisibility) {
3080 return;
3083 // XXXndeakin this doesn't seem right. It should be checking for this only
3084 // on the nearest ancestor frame which is a chrome frame. But this is
3085 // what the existing code does, so just leave it for now.
3086 if (!browseWithCaret) {
3087 nsCOMPtr<Element> docElement = mFocusedWindow->GetFrameElementInternal();
3088 if (docElement)
3089 browseWithCaret = docElement->AttrValueIs(
3090 kNameSpaceID_None, nsGkAtoms::showcaret, u"true"_ns, eCaseMatters);
3093 SetCaretVisible(presShell, browseWithCaret, aContent);
3096 void nsFocusManager::MoveCaretToFocus(PresShell* aPresShell,
3097 nsIContent* aContent) {
3098 nsCOMPtr<Document> doc = aPresShell->GetDocument();
3099 if (doc) {
3100 RefPtr<nsFrameSelection> frameSelection = aPresShell->FrameSelection();
3101 RefPtr<Selection> domSelection =
3102 frameSelection->GetSelection(SelectionType::eNormal);
3103 if (domSelection) {
3104 // First clear the selection. This way, if there is no currently focused
3105 // content, the selection will just be cleared.
3106 domSelection->RemoveAllRanges(IgnoreErrors());
3107 if (aContent) {
3108 ErrorResult rv;
3109 RefPtr<nsRange> newRange = doc->CreateRange(rv);
3110 if (NS_WARN_IF(rv.Failed())) {
3111 rv.SuppressException();
3112 return;
3115 // Set the range to the start of the currently focused node
3116 // Make sure it's collapsed
3117 newRange->SelectNodeContents(*aContent, IgnoreErrors());
3119 if (!aContent->GetFirstChild() ||
3120 aContent->IsHTMLFormControlElement()) {
3121 // If current focus node is a leaf, set range to before the
3122 // node by using the parent as a container.
3123 // This prevents it from appearing as selected.
3124 newRange->SetStartBefore(*aContent, IgnoreErrors());
3125 newRange->SetEndBefore(*aContent, IgnoreErrors());
3127 domSelection->AddRangeAndSelectFramesAndNotifyListeners(*newRange,
3128 IgnoreErrors());
3129 domSelection->CollapseToStart(IgnoreErrors());
3135 nsresult nsFocusManager::SetCaretVisible(PresShell* aPresShell, bool aVisible,
3136 nsIContent* aContent) {
3137 // When browsing with caret, make sure caret is visible after new focus
3138 // Return early if there is no caret. This can happen for the testcase
3139 // for bug 308025 where a window is closed in a blur handler.
3140 RefPtr<nsCaret> caret = aPresShell->GetCaret();
3141 if (!caret) {
3142 return NS_OK;
3145 bool caretVisible = caret->IsVisible();
3146 if (!aVisible && !caretVisible) {
3147 return NS_OK;
3150 RefPtr<nsFrameSelection> frameSelection;
3151 if (aContent) {
3152 NS_ASSERTION(aContent->GetComposedDoc() == aPresShell->GetDocument(),
3153 "Wrong document?");
3154 nsIFrame* focusFrame = aContent->GetPrimaryFrame();
3155 if (focusFrame) {
3156 frameSelection = focusFrame->GetFrameSelection();
3160 RefPtr<nsFrameSelection> docFrameSelection = aPresShell->FrameSelection();
3162 if (docFrameSelection && caret &&
3163 (frameSelection == docFrameSelection || !aContent)) {
3164 Selection* domSelection =
3165 docFrameSelection->GetSelection(SelectionType::eNormal);
3166 if (domSelection) {
3167 // First, hide the caret to prevent attempting to show it in
3168 // SetCaretDOMSelection
3169 aPresShell->SetCaretEnabled(false);
3171 // Caret must blink on non-editable elements
3172 caret->SetIgnoreUserModify(true);
3173 // Tell the caret which selection to use
3174 caret->SetSelection(domSelection);
3176 // In content, we need to set the caret. The only special case is edit
3177 // fields, which have a different frame selection from the document.
3178 // They will take care of making the caret visible themselves.
3180 aPresShell->SetCaretReadOnly(false);
3181 aPresShell->SetCaretEnabled(aVisible);
3185 return NS_OK;
3188 void nsFocusManager::GetSelectionLocation(Document* aDocument,
3189 PresShell* aPresShell,
3190 nsIContent** aStartContent,
3191 nsIContent** aEndContent) {
3192 *aStartContent = *aEndContent = nullptr;
3194 nsPresContext* presContext = aPresShell->GetPresContext();
3195 NS_ASSERTION(presContext, "mPresContent is null!!");
3197 RefPtr<Selection> domSelection =
3198 aPresShell->ConstFrameSelection()->GetSelection(SelectionType::eNormal);
3199 if (!domSelection) {
3200 return;
3203 const nsRange* domRange = domSelection->GetRangeAt(0);
3204 if (!domRange || !domRange->IsPositioned()) {
3205 return;
3207 nsIContent* start = nsIContent::FromNode(domRange->GetStartContainer());
3208 nsIContent* end = nsIContent::FromNode(domRange->GetEndContainer());
3209 if (nsIContent* child = domRange->StartRef().GetChildAtOffset()) {
3210 start = child;
3212 if (nsIContent* child = domRange->EndRef().GetChildAtOffset()) {
3213 end = child;
3216 // Next check to see if our caret is at the very end of a text node. If so,
3217 // the caret is actually sitting in front of the next logical frame's primary
3218 // node - so for this case we need to change the content to that node.
3219 // Note that if the text does not have text frame, we do not need to retreive
3220 // caret frame. This could occur if text frame has only collapsisble white-
3221 // spaces and is around a block boundary or an ancestor of it is invisible.
3222 // XXX If there is a visible text sibling, should we return it in the former
3223 // case?
3224 if (auto* text = Text::FromNodeOrNull(start);
3225 text && text->GetPrimaryFrame() &&
3226 text->TextDataLength() == domRange->StartOffset() &&
3227 domSelection->IsCollapsed()) {
3228 nsIFrame* startFrame = start->GetPrimaryFrame();
3229 // Yes, indeed we were at the end of the last node
3230 nsIFrame* limiter =
3231 domSelection && domSelection->GetAncestorLimiter()
3232 ? domSelection->GetAncestorLimiter()->GetPrimaryFrame()
3233 : nullptr;
3234 nsFrameIterator frameIterator(presContext, startFrame,
3235 nsFrameIterator::Type::Leaf,
3236 false, // aVisual
3237 false, // aLockInScrollView
3238 true, // aFollowOOFs
3239 false, // aSkipPopupChecks
3240 limiter);
3242 nsIFrame* newCaretFrame = nullptr;
3243 nsIContent* newCaretContent = start;
3244 const bool endOfSelectionInStartNode = start == end;
3245 do {
3246 // Continue getting the next frame until the primary content for the
3247 // frame we are on changes - we don't want to be stuck in the same
3248 // place
3249 frameIterator.Next();
3250 newCaretFrame = frameIterator.CurrentItem();
3251 if (!newCaretFrame) {
3252 break;
3254 newCaretContent = newCaretFrame->GetContent();
3255 } while (!newCaretContent || newCaretContent == start);
3257 if (newCaretFrame && newCaretContent) {
3258 // If the caret is exactly at the same position of the new frame,
3259 // then we can use the newCaretFrame and newCaretContent for our
3260 // position
3261 nsRect caretRect;
3262 if (nsIFrame* frame = nsCaret::GetGeometry(domSelection, &caretRect)) {
3263 nsPoint caretWidgetOffset;
3264 nsIWidget* widget = frame->GetNearestWidget(caretWidgetOffset);
3265 caretRect.MoveBy(caretWidgetOffset);
3266 nsPoint newCaretOffset;
3267 nsIWidget* newCaretWidget =
3268 newCaretFrame->GetNearestWidget(newCaretOffset);
3269 if (widget == newCaretWidget && caretRect.TopLeft() == newCaretOffset) {
3270 // The caret is at the start of the new element.
3271 startFrame = newCaretFrame;
3272 start = newCaretContent;
3273 if (endOfSelectionInStartNode) {
3274 end = newCaretContent; // Ensure end of selection is
3275 // not before start
3282 NS_IF_ADDREF(*aStartContent = start);
3283 NS_IF_ADDREF(*aEndContent = end);
3286 nsresult nsFocusManager::DetermineElementToMoveFocus(
3287 nsPIDOMWindowOuter* aWindow, nsIContent* aStartContent, int32_t aType,
3288 bool aNoParentTraversal, bool aNavigateByKey, nsIContent** aNextContent) {
3289 *aNextContent = nullptr;
3291 // This is used for document navigation only. It will be set to true if we
3292 // start navigating from a starting point. If this starting point is near the
3293 // end of the document (for example, an element on a statusbar), and there
3294 // are no child documents or panels before the end of the document, then we
3295 // will need to ensure that we don't consider the root chrome window when we
3296 // loop around and instead find the next child document/panel, as focus is
3297 // already in that window. This flag will be cleared once we navigate into
3298 // another document.
3299 bool mayFocusRoot = (aStartContent != nullptr);
3301 nsCOMPtr<nsIContent> startContent = aStartContent;
3302 if (!startContent && aType != MOVEFOCUS_CARET) {
3303 if (aType == MOVEFOCUS_FORWARDDOC || aType == MOVEFOCUS_BACKWARDDOC) {
3304 // When moving between documents, make sure to get the right
3305 // starting content in a descendant.
3306 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
3307 startContent = GetFocusedDescendant(aWindow, eIncludeAllDescendants,
3308 getter_AddRefs(focusedWindow));
3309 } else if (aType != MOVEFOCUS_LASTDOC) {
3310 // Otherwise, start at the focused node. If MOVEFOCUS_LASTDOC is used,
3311 // then we are document-navigating backwards from chrome to the content
3312 // process, and we don't want to use this so that we start from the end
3313 // of the document.
3314 startContent = aWindow->GetFocusedElement();
3318 nsCOMPtr<Document> doc;
3319 if (startContent)
3320 doc = startContent->GetComposedDoc();
3321 else
3322 doc = aWindow->GetExtantDoc();
3323 if (!doc) return NS_OK;
3325 LookAndFeel::GetInt(LookAndFeel::IntID::TabFocusModel,
3326 &nsIContent::sTabFocusModel);
3328 // True if we are navigating by document (F6/Shift+F6) or false if we are
3329 // navigating by element (Tab/Shift+Tab).
3330 const bool forDocumentNavigation =
3331 aType == MOVEFOCUS_FORWARDDOC || aType == MOVEFOCUS_BACKWARDDOC ||
3332 aType == MOVEFOCUS_FIRSTDOC || aType == MOVEFOCUS_LASTDOC;
3334 // If moving to the root or first document, find the root element and return.
3335 if (aType == MOVEFOCUS_ROOT || aType == MOVEFOCUS_FIRSTDOC) {
3336 NS_IF_ADDREF(*aNextContent = GetRootForFocus(aWindow, doc, false, false));
3337 if (!*aNextContent && aType == MOVEFOCUS_FIRSTDOC) {
3338 // When looking for the first document, if the root wasn't focusable,
3339 // find the next focusable document.
3340 aType = MOVEFOCUS_FORWARDDOC;
3341 } else {
3342 return NS_OK;
3346 // rootElement and presShell may be set to sub-document's ones so that they
3347 // cannot be `const`.
3348 RefPtr<Element> rootElement = doc->GetRootElement();
3349 NS_ENSURE_TRUE(rootElement, NS_OK);
3351 RefPtr<PresShell> presShell = doc->GetPresShell();
3352 NS_ENSURE_TRUE(presShell, NS_OK);
3354 if (aType == MOVEFOCUS_FIRST) {
3355 if (!aStartContent) {
3356 startContent = rootElement;
3358 return GetNextTabbableContent(presShell, startContent, nullptr,
3359 startContent, true, 1, false, false,
3360 aNavigateByKey, false, aNextContent);
3362 if (aType == MOVEFOCUS_LAST) {
3363 if (!aStartContent) {
3364 startContent = rootElement;
3366 return GetNextTabbableContent(presShell, startContent, nullptr,
3367 startContent, false, 0, false, false,
3368 aNavigateByKey, false, aNextContent);
3371 bool forward = (aType == MOVEFOCUS_FORWARD || aType == MOVEFOCUS_FORWARDDOC ||
3372 aType == MOVEFOCUS_CARET);
3373 bool doNavigation = true;
3374 bool ignoreTabIndex = false;
3375 // when a popup is open, we want to ensure that tab navigation occurs only
3376 // within the most recently opened panel. If a popup is open, its frame will
3377 // be stored in popupFrame.
3378 nsIFrame* popupFrame = nullptr;
3380 int32_t tabIndex = forward ? 1 : 0;
3381 if (startContent) {
3382 nsIFrame* frame = startContent->GetPrimaryFrame();
3383 tabIndex = (frame && !startContent->IsHTMLElement(nsGkAtoms::area))
3384 ? frame->IsFocusable().mTabIndex
3385 : startContent->IsFocusableWithoutStyle().mTabIndex;
3387 // if the current element isn't tabbable, ignore the tabindex and just
3388 // look for the next element. The root content won't have a tabindex
3389 // so just treat this as the beginning of the tab order.
3390 if (tabIndex < 0) {
3391 tabIndex = 1;
3392 if (startContent != rootElement) {
3393 ignoreTabIndex = true;
3397 // check if the focus is currently inside a popup. Elements such as the
3398 // autocomplete widget use the noautofocus attribute to allow the focus to
3399 // remain outside the popup when it is opened.
3400 if (frame) {
3401 popupFrame = nsLayoutUtils::GetClosestFrameOfType(
3402 frame, LayoutFrameType::MenuPopup);
3405 if (popupFrame && !forDocumentNavigation) {
3406 // Don't navigate outside of a popup, so pretend that the
3407 // root content is the popup itself
3408 rootElement = popupFrame->GetContent()->AsElement();
3409 NS_ASSERTION(rootElement, "Popup frame doesn't have a content node");
3410 } else if (!forward) {
3411 // If focus moves backward and when current focused node is root
3412 // content or <body> element which is editable by contenteditable
3413 // attribute, focus should move to its parent document.
3414 if (startContent == rootElement) {
3415 doNavigation = false;
3416 } else {
3417 Document* doc = startContent->GetComposedDoc();
3418 if (startContent ==
3419 nsLayoutUtils::GetEditableRootContentByContentEditable(doc)) {
3420 doNavigation = false;
3424 } else {
3425 if (aType != MOVEFOCUS_CARET) {
3426 // if there is no focus, yet a panel is open, focus the first item in
3427 // the panel
3428 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
3429 if (pm) {
3430 popupFrame = pm->GetTopPopup(PopupType::Panel);
3433 if (popupFrame) {
3434 // When there is a popup open, and no starting content, start the search
3435 // at the topmost popup.
3436 startContent = popupFrame->GetContent();
3437 NS_ASSERTION(startContent, "Popup frame doesn't have a content node");
3438 // Unless we are searching for documents, set the root content to the
3439 // popup as well, so that we don't tab-navigate outside the popup.
3440 // When navigating by documents, we start at the popup but can navigate
3441 // outside of it to look for other panels and documents.
3442 if (!forDocumentNavigation) {
3443 rootElement = startContent->AsElement();
3446 doc = startContent ? startContent->GetComposedDoc() : nullptr;
3447 } else {
3448 // Otherwise, for content shells, start from the location of the caret.
3449 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
3450 if (docShell && docShell->ItemType() != nsIDocShellTreeItem::typeChrome) {
3451 nsCOMPtr<nsIContent> endSelectionContent;
3452 GetSelectionLocation(doc, presShell, getter_AddRefs(startContent),
3453 getter_AddRefs(endSelectionContent));
3454 // If the selection is on the rootElement, then there is no selection
3455 if (startContent == rootElement) {
3456 startContent = nullptr;
3459 if (aType == MOVEFOCUS_CARET) {
3460 // GetFocusInSelection finds a focusable link near the caret.
3461 // If there is no start content though, don't do this to avoid
3462 // focusing something unexpected.
3463 if (startContent) {
3464 GetFocusInSelection(aWindow, startContent, endSelectionContent,
3465 aNextContent);
3467 return NS_OK;
3470 if (startContent) {
3471 // when starting from a selection, we always want to find the next or
3472 // previous element in the document. So the tabindex on elements
3473 // should be ignored.
3474 ignoreTabIndex = true;
3478 if (!startContent) {
3479 // otherwise, just use the root content as the starting point
3480 startContent = rootElement;
3481 NS_ENSURE_TRUE(startContent, NS_OK);
3486 // Check if the starting content is the same as the content assigned to the
3487 // retargetdocumentfocus attribute. Is so, we don't want to start searching
3488 // from there but instead from the beginning of the document. Otherwise, the
3489 // content that appears before the retargetdocumentfocus element will never
3490 // get checked as it will be skipped when the focus is retargetted to it.
3491 if (forDocumentNavigation && nsContentUtils::IsChromeDoc(doc)) {
3492 nsAutoString retarget;
3494 if (rootElement->GetAttr(nsGkAtoms::retargetdocumentfocus, retarget)) {
3495 nsIContent* retargetElement = doc->GetElementById(retarget);
3496 // The common case here is the urlbar where focus is on the anonymous
3497 // input inside the textbox, but the retargetdocumentfocus attribute
3498 // refers to the textbox. The Contains check will return false and the
3499 // IsInclusiveDescendantOf check will return true in this case.
3500 if (retargetElement &&
3501 (retargetElement == startContent ||
3502 (!retargetElement->Contains(startContent) &&
3503 startContent->IsInclusiveDescendantOf(retargetElement)))) {
3504 startContent = rootElement;
3509 NS_ASSERTION(startContent, "starting content not set");
3511 // keep a reference to the starting content. If we find that again, it means
3512 // we've iterated around completely and we don't want to adjust the focus.
3513 // The skipOriginalContentCheck will be set to true only for the first time
3514 // GetNextTabbableContent is called. This ensures that we don't break out
3515 // when nothing is focused to start with. Specifically,
3516 // GetNextTabbableContent first checks the root content -- which happens to
3517 // be the same as the start content -- when nothing is focused and tabbing
3518 // forward. Without skipOriginalContentCheck set to true, we'd end up
3519 // returning right away and focusing nothing. Luckily, GetNextTabbableContent
3520 // will never wrap around on its own, and can only return the original
3521 // content when it is called a second time or later.
3522 bool skipOriginalContentCheck = true;
3523 const nsCOMPtr<nsIContent> originalStartContent = startContent;
3525 LOGCONTENTNAVIGATION("Focus Navigation Start Content %s", startContent.get());
3526 LOGFOCUSNAVIGATION((" Forward: %d Tabindex: %d Ignore: %d DocNav: %d",
3527 forward, tabIndex, ignoreTabIndex,
3528 forDocumentNavigation));
3530 while (doc) {
3531 if (doNavigation) {
3532 nsCOMPtr<nsIContent> nextFocus;
3533 // TODO: MOZ_KnownLive is reruired due to bug 1770680
3534 nsresult rv = GetNextTabbableContent(
3535 presShell, rootElement,
3536 MOZ_KnownLive(skipOriginalContentCheck ? nullptr
3537 : originalStartContent.get()),
3538 startContent, forward, tabIndex, ignoreTabIndex,
3539 forDocumentNavigation, aNavigateByKey, false,
3540 getter_AddRefs(nextFocus));
3541 NS_ENSURE_SUCCESS(rv, rv);
3542 if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
3543 // Navigation was redirected to a child process, so just return.
3544 return NS_OK;
3547 // found a content node to focus.
3548 if (nextFocus) {
3549 LOGCONTENTNAVIGATION("Next Content: %s", nextFocus.get());
3551 // as long as the found node was not the same as the starting node,
3552 // set it as the return value. For document navigation, we can return
3553 // the same element in case there is only one content node that could
3554 // be returned, for example, in a child process document.
3555 if (nextFocus != originalStartContent || forDocumentNavigation) {
3556 nextFocus.forget(aNextContent);
3558 return NS_OK;
3561 if (popupFrame && !forDocumentNavigation) {
3562 // in a popup, so start again from the beginning of the popup. However,
3563 // if we already started at the beginning, then there isn't anything to
3564 // focus, so just return
3565 if (startContent != rootElement) {
3566 startContent = rootElement;
3567 tabIndex = forward ? 1 : 0;
3568 continue;
3570 return NS_OK;
3574 doNavigation = true;
3575 skipOriginalContentCheck = forDocumentNavigation;
3576 ignoreTabIndex = false;
3578 if (aNoParentTraversal) {
3579 if (startContent == rootElement) {
3580 return NS_OK;
3583 startContent = rootElement;
3584 tabIndex = forward ? 1 : 0;
3585 continue;
3588 // Reached the beginning or end of the document. Next, navigate up to the
3589 // parent document and try again.
3590 nsCOMPtr<nsPIDOMWindowOuter> piWindow = doc->GetWindow();
3591 NS_ENSURE_TRUE(piWindow, NS_ERROR_FAILURE);
3593 nsCOMPtr<nsIDocShell> docShell = piWindow->GetDocShell();
3594 NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
3596 // Get the frame element this window is inside and, from that, get the
3597 // parent document and presshell. If there is no enclosing frame element,
3598 // then this is a top-level, embedded or remote window.
3599 startContent = piWindow->GetFrameElementInternal();
3600 if (startContent) {
3601 doc = startContent->GetComposedDoc();
3602 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
3604 rootElement = doc->GetRootElement();
3605 presShell = doc->GetPresShell();
3607 // We can focus the root element now that we have moved to another
3608 // document.
3609 mayFocusRoot = true;
3611 nsIFrame* frame = startContent->GetPrimaryFrame();
3612 if (!frame) {
3613 return NS_OK;
3616 tabIndex = frame->IsFocusable().mTabIndex;
3617 if (tabIndex < 0) {
3618 tabIndex = 1;
3619 ignoreTabIndex = true;
3622 // if the frame is inside a popup, make sure to scan only within the
3623 // popup. This handles the situation of tabbing amongst elements
3624 // inside an iframe which is itself inside a popup. Otherwise,
3625 // navigation would move outside the popup when tabbing outside the
3626 // iframe.
3627 if (!forDocumentNavigation) {
3628 popupFrame = nsLayoutUtils::GetClosestFrameOfType(
3629 frame, LayoutFrameType::MenuPopup);
3630 if (popupFrame) {
3631 rootElement = popupFrame->GetContent()->AsElement();
3632 NS_ASSERTION(rootElement, "Popup frame doesn't have a content node");
3635 } else {
3636 if (aNavigateByKey) {
3637 // There is no parent, so call the tree owner. This will tell the
3638 // embedder or parent process that it should take the focus.
3639 bool tookFocus;
3640 docShell->TabToTreeOwner(forward, forDocumentNavigation, &tookFocus);
3641 // If the tree owner took the focus, blur the current element.
3642 if (tookFocus) {
3643 RefPtr<BrowsingContext> focusedBC = GetFocusedBrowsingContext();
3644 if (focusedBC && focusedBC->IsInProcess()) {
3645 Blur(focusedBC, nullptr, true, true, false,
3646 GenerateFocusActionId());
3647 } else {
3648 nsCOMPtr<nsPIDOMWindowOuter> window = docShell->GetWindow();
3649 window->SetFocusedElement(nullptr);
3651 return NS_OK;
3655 // If we have reached the end of the top-level document, focus the
3656 // first element in the top-level document. This should always happen
3657 // when navigating by document forwards but when navigating backwards,
3658 // only do this if we started in another document or within a popup frame.
3659 // If the focus started in this window outside a popup however, we should
3660 // continue by looping around to the end again.
3661 if (forDocumentNavigation && (forward || mayFocusRoot || popupFrame)) {
3662 // HTML content documents can have their root element focused (a focus
3663 // ring appears around the entire content area frame). This root
3664 // appears in the tab order before all of the elements in the document.
3665 // Chrome documents however cannot be focused directly, so instead we
3666 // focus the first focusable element within the window.
3667 // For example, the urlbar.
3668 RefPtr<Element> rootElementForFocus =
3669 GetRootForFocus(piWindow, doc, true, true);
3670 return FocusFirst(rootElementForFocus, aNextContent);
3673 // Once we have hit the top-level and have iterated to the end again, we
3674 // just want to break out next time we hit this spot to prevent infinite
3675 // iteration.
3676 mayFocusRoot = true;
3678 // reset the tab index and start again from the beginning or end
3679 startContent = rootElement;
3680 tabIndex = forward ? 1 : 0;
3683 // wrapped all the way around and didn't find anything to move the focus
3684 // to, so just break out
3685 if (startContent == originalStartContent) {
3686 break;
3690 return NS_OK;
3693 uint32_t nsFocusManager::ProgrammaticFocusFlags(const FocusOptions& aOptions) {
3694 uint32_t flags = FLAG_BYJS;
3695 if (aOptions.mPreventScroll) {
3696 flags |= FLAG_NOSCROLL;
3698 if (aOptions.mFocusVisible.WasPassed()) {
3699 flags |= aOptions.mFocusVisible.Value() ? FLAG_SHOWRING : FLAG_NOSHOWRING;
3701 if (UserActivation::IsHandlingKeyboardInput()) {
3702 flags |= FLAG_BYKEY;
3704 // TODO: We could do a similar thing if we're handling mouse input, but that
3705 // changes focusability of some elements so may be more risky.
3706 return flags;
3709 static bool IsHostOrSlot(const nsIContent* aContent) {
3710 return aContent && (aContent->GetShadowRoot() ||
3711 aContent->IsHTMLElement(nsGkAtoms::slot));
3714 // Helper class to iterate contents in scope by traversing flattened tree
3715 // in tree order
3716 class MOZ_STACK_CLASS ScopedContentTraversal {
3717 public:
3718 ScopedContentTraversal(nsIContent* aStartContent, nsIContent* aOwner)
3719 : mCurrent(aStartContent), mOwner(aOwner) {
3720 MOZ_ASSERT(aStartContent);
3723 void Next();
3724 void Prev();
3726 void Reset() { SetCurrent(mOwner); }
3728 nsIContent* GetCurrent() const { return mCurrent; }
3730 private:
3731 void SetCurrent(nsIContent* aContent) { mCurrent = aContent; }
3733 nsIContent* mCurrent;
3734 nsIContent* mOwner;
3737 void ScopedContentTraversal::Next() {
3738 MOZ_ASSERT(mCurrent);
3740 // Get mCurrent's first child if it's in the same scope.
3741 if (!IsHostOrSlot(mCurrent) || mCurrent == mOwner) {
3742 StyleChildrenIterator iter(mCurrent);
3743 nsIContent* child = iter.GetNextChild();
3744 if (child) {
3745 SetCurrent(child);
3746 return;
3750 // If mOwner has no children, END traversal
3751 if (mCurrent == mOwner) {
3752 SetCurrent(nullptr);
3753 return;
3756 nsIContent* current = mCurrent;
3757 while (1) {
3758 // Create parent's iterator and move to current
3759 nsIContent* parent = current->GetFlattenedTreeParent();
3760 StyleChildrenIterator parentIter(parent);
3761 parentIter.Seek(current);
3763 // Get next sibling of current
3764 if (nsIContent* next = parentIter.GetNextChild()) {
3765 SetCurrent(next);
3766 return;
3769 // If no next sibling and parent is mOwner, END traversal
3770 if (parent == mOwner) {
3771 SetCurrent(nullptr);
3772 return;
3775 current = parent;
3779 void ScopedContentTraversal::Prev() {
3780 MOZ_ASSERT(mCurrent);
3782 nsIContent* parent;
3783 nsIContent* last;
3784 if (mCurrent == mOwner) {
3785 // Get last child of mOwner
3786 StyleChildrenIterator ownerIter(mOwner, false /* aStartAtBeginning */);
3787 last = ownerIter.GetPreviousChild();
3789 parent = last;
3790 } else {
3791 // Create parent's iterator and move to mCurrent
3792 parent = mCurrent->GetFlattenedTreeParent();
3793 StyleChildrenIterator parentIter(parent);
3794 parentIter.Seek(mCurrent);
3796 // Get previous sibling
3797 last = parentIter.GetPreviousChild();
3800 while (last) {
3801 parent = last;
3802 if (IsHostOrSlot(parent)) {
3803 // Skip contents in other scopes
3804 break;
3807 // Find last child
3808 StyleChildrenIterator iter(parent, false /* aStartAtBeginning */);
3809 last = iter.GetPreviousChild();
3812 // If parent is mOwner and no previous sibling remains, END traversal
3813 SetCurrent(parent == mOwner ? nullptr : parent);
3816 static bool IsOpenPopoverWithInvoker(nsIContent* aContent) {
3817 if (auto* popover = Element::FromNode(aContent)) {
3818 return popover && popover->IsPopoverOpen() &&
3819 popover->GetPopoverData()->GetInvoker();
3821 return false;
3824 static nsIContent* InvokerForPopoverShowingState(nsIContent* aContent) {
3825 Element* invoker = Element::FromNode(aContent);
3826 if (!invoker) {
3827 return nullptr;
3830 nsGenericHTMLElement* popover = invoker->GetEffectivePopoverTargetElement();
3831 if (popover && popover->IsPopoverOpen() &&
3832 popover->GetPopoverData()->GetInvoker() == invoker) {
3833 return aContent;
3836 return nullptr;
3840 * Returns scope owner of aContent.
3841 * A scope owner is either a shadow host, or slot.
3843 static nsIContent* FindScopeOwner(nsIContent* aContent) {
3844 nsIContent* currentContent = aContent;
3845 while (currentContent) {
3846 nsIContent* parent = currentContent->GetFlattenedTreeParent();
3848 // Shadow host / Slot
3849 if (IsHostOrSlot(parent)) {
3850 return parent;
3853 currentContent = parent;
3856 return nullptr;
3860 * Host and Slot elements need to be handled as if they had tabindex 0 even
3861 * when they don't have the attribute. This is a helper method to get the
3862 * right value for focus navigation. If aIsFocusable is passed, it is set to
3863 * true if the element itself is focusable.
3865 static int32_t HostOrSlotTabIndexValue(const nsIContent* aContent,
3866 bool* aIsFocusable = nullptr) {
3867 MOZ_ASSERT(IsHostOrSlot(aContent));
3869 if (aIsFocusable) {
3870 nsIFrame* frame = aContent->GetPrimaryFrame();
3871 *aIsFocusable = frame && frame->IsFocusable().mTabIndex >= 0;
3874 const nsAttrValue* attrVal =
3875 aContent->AsElement()->GetParsedAttr(nsGkAtoms::tabindex);
3876 if (!attrVal) {
3877 return 0;
3880 if (attrVal->Type() == nsAttrValue::eInteger) {
3881 return attrVal->GetIntegerValue();
3884 return -1;
3887 nsIContent* nsFocusManager::GetNextTabbableContentInScope(
3888 nsIContent* aOwner, nsIContent* aStartContent,
3889 nsIContent* aOriginalStartContent, bool aForward, int32_t aCurrentTabIndex,
3890 bool aIgnoreTabIndex, bool aForDocumentNavigation, bool aNavigateByKey,
3891 bool aSkipOwner) {
3892 MOZ_ASSERT(
3893 IsHostOrSlot(aOwner) || IsOpenPopoverWithInvoker(aOwner),
3894 "Scope owner should be host, slot or an open popover with invoker set.");
3896 // XXX: Why don't we ignore tabindex when the current tabindex < 0?
3897 MOZ_ASSERT_IF(aCurrentTabIndex < 0, aIgnoreTabIndex);
3899 if (!aSkipOwner && (aForward && aOwner == aStartContent)) {
3900 if (nsIFrame* frame = aOwner->GetPrimaryFrame()) {
3901 auto focusable = frame->IsFocusable();
3902 if (focusable && focusable.mTabIndex >= 0) {
3903 return aOwner;
3909 // Iterate contents in scope
3911 ScopedContentTraversal contentTraversal(aStartContent, aOwner);
3912 nsCOMPtr<nsIContent> iterContent;
3913 nsIContent* firstNonChromeOnly =
3914 aStartContent->IsInNativeAnonymousSubtree()
3915 ? aStartContent->FindFirstNonChromeOnlyAccessContent()
3916 : nullptr;
3917 while (1) {
3918 // Iterate tab index to find corresponding contents in scope
3920 while (1) {
3921 // Iterate remaining contents in scope to find next content to focus
3923 // Get next content
3924 aForward ? contentTraversal.Next() : contentTraversal.Prev();
3925 iterContent = contentTraversal.GetCurrent();
3927 if (firstNonChromeOnly && firstNonChromeOnly == iterContent) {
3928 // We just broke out from the native anonymous content, so move
3929 // to the previous/next node of the native anonymous owner.
3930 if (aForward) {
3931 contentTraversal.Next();
3932 } else {
3933 contentTraversal.Prev();
3935 iterContent = contentTraversal.GetCurrent();
3937 if (!iterContent) {
3938 // Reach the end
3939 break;
3942 int32_t tabIndex = 0;
3943 if (iterContent->IsInNativeAnonymousSubtree() &&
3944 iterContent->GetPrimaryFrame()) {
3945 tabIndex = iterContent->GetPrimaryFrame()->IsFocusable().mTabIndex;
3946 } else if (IsHostOrSlot(iterContent)) {
3947 tabIndex = HostOrSlotTabIndexValue(iterContent);
3948 } else {
3949 nsIFrame* frame = iterContent->GetPrimaryFrame();
3950 if (!frame) {
3951 continue;
3953 tabIndex = frame->IsFocusable().mTabIndex;
3955 if (tabIndex < 0 || !(aIgnoreTabIndex || tabIndex == aCurrentTabIndex)) {
3956 continue;
3959 if (!IsHostOrSlot(iterContent)) {
3960 nsCOMPtr<nsIContent> elementInFrame;
3961 bool checkSubDocument = true;
3962 if (aForDocumentNavigation &&
3963 TryDocumentNavigation(iterContent, &checkSubDocument,
3964 getter_AddRefs(elementInFrame))) {
3965 return elementInFrame;
3967 if (!checkSubDocument) {
3968 continue;
3971 if (TryToMoveFocusToSubDocument(iterContent, aOriginalStartContent,
3972 aForward, aForDocumentNavigation,
3973 aNavigateByKey,
3974 getter_AddRefs(elementInFrame))) {
3975 return elementInFrame;
3978 // Found content to focus
3979 return iterContent;
3982 // Search in scope owned by iterContent
3983 nsIContent* contentToFocus = GetNextTabbableContentInScope(
3984 iterContent, iterContent, aOriginalStartContent, aForward,
3985 aForward ? 1 : 0, aIgnoreTabIndex, aForDocumentNavigation,
3986 aNavigateByKey, false /* aSkipOwner */);
3987 if (contentToFocus) {
3988 return contentToFocus;
3992 // If already at lowest priority tab (0), end search completely.
3993 // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
3994 if (aCurrentTabIndex == (aForward ? 0 : 1)) {
3995 break;
3998 // We've been just trying to find some focusable element, and haven't, so
3999 // bail out.
4000 if (aIgnoreTabIndex) {
4001 break;
4004 // Continue looking for next highest priority tabindex
4005 aCurrentTabIndex = GetNextTabIndex(aOwner, aCurrentTabIndex, aForward);
4006 contentTraversal.Reset();
4009 // Return scope owner at last for backward navigation if its tabindex
4010 // is non-negative
4011 if (!aSkipOwner && !aForward) {
4012 if (nsIFrame* frame = aOwner->GetPrimaryFrame()) {
4013 auto focusable = frame->IsFocusable();
4014 if (focusable && focusable.mTabIndex >= 0) {
4015 return aOwner;
4020 return nullptr;
4023 nsIContent* nsFocusManager::GetNextTabbableContentInAncestorScopes(
4024 nsIContent* aStartOwner, nsCOMPtr<nsIContent>& aStartContent /* inout */,
4025 nsIContent* aOriginalStartContent, bool aForward, int32_t* aCurrentTabIndex,
4026 bool* aIgnoreTabIndex, bool aForDocumentNavigation, bool aNavigateByKey) {
4027 MOZ_ASSERT(aStartOwner == FindScopeOwner(aStartContent),
4028 "aStartOWner should be the scope owner of aStartContent");
4029 MOZ_ASSERT(IsHostOrSlot(aStartOwner), "scope owner should be host or slot");
4031 nsCOMPtr<nsIContent> owner = aStartOwner;
4032 nsCOMPtr<nsIContent> startContent = aStartContent;
4033 while (IsHostOrSlot(owner)) {
4034 int32_t tabIndex = 0;
4035 if (IsHostOrSlot(startContent)) {
4036 tabIndex = HostOrSlotTabIndexValue(startContent);
4037 } else if (nsIFrame* frame = startContent->GetPrimaryFrame()) {
4038 tabIndex = frame->IsFocusable().mTabIndex;
4039 } else {
4040 tabIndex = startContent->IsFocusableWithoutStyle().mTabIndex;
4042 nsIContent* contentToFocus = GetNextTabbableContentInScope(
4043 owner, startContent, aOriginalStartContent, aForward, tabIndex,
4044 tabIndex < 0, aForDocumentNavigation, aNavigateByKey,
4045 false /* aSkipOwner */);
4046 if (contentToFocus) {
4047 return contentToFocus;
4050 startContent = owner;
4051 owner = FindScopeOwner(startContent);
4054 // If not found in shadow DOM, search from the top level shadow host in light
4055 // DOM
4056 aStartContent = startContent;
4057 *aCurrentTabIndex = HostOrSlotTabIndexValue(startContent);
4059 if (*aCurrentTabIndex < 0) {
4060 *aIgnoreTabIndex = true;
4063 return nullptr;
4066 static nsIContent* GetTopLevelScopeOwner(nsIContent* aContent) {
4067 nsIContent* topLevelScopeOwner = nullptr;
4068 while (aContent) {
4069 if (HTMLSlotElement* slot = aContent->GetAssignedSlot()) {
4070 aContent = slot;
4071 topLevelScopeOwner = aContent;
4072 } else if (ShadowRoot* shadowRoot = aContent->GetContainingShadow()) {
4073 aContent = shadowRoot->Host();
4074 topLevelScopeOwner = aContent;
4075 } else {
4076 aContent = aContent->GetParent();
4077 if (aContent && (HTMLSlotElement::FromNode(aContent) ||
4078 IsOpenPopoverWithInvoker(aContent))) {
4079 topLevelScopeOwner = aContent;
4084 return topLevelScopeOwner;
4087 nsresult nsFocusManager::GetNextTabbableContent(
4088 PresShell* aPresShell, nsIContent* aRootContent,
4089 nsIContent* aOriginalStartContent, nsIContent* aStartContent, bool aForward,
4090 int32_t aCurrentTabIndex, bool aIgnoreTabIndex, bool aForDocumentNavigation,
4091 bool aNavigateByKey, bool aSkipPopover, nsIContent** aResultContent) {
4092 *aResultContent = nullptr;
4094 if (!aStartContent) {
4095 return NS_OK;
4098 nsCOMPtr<nsIContent> startContent = aStartContent;
4099 nsCOMPtr<nsIContent> currentTopLevelScopeOwner =
4100 GetTopLevelScopeOwner(startContent);
4102 LOGCONTENTNAVIGATION("GetNextTabbable: %s", startContent);
4103 LOGFOCUSNAVIGATION((" tabindex: %d", aCurrentTabIndex));
4105 // If startContent is a shadow host or slot in forward navigation,
4106 // search in scope owned by startContent
4107 if (aForward && IsHostOrSlot(startContent)) {
4108 nsIContent* contentToFocus = GetNextTabbableContentInScope(
4109 startContent, startContent, aOriginalStartContent, aForward, 1,
4110 aIgnoreTabIndex, aForDocumentNavigation, aNavigateByKey,
4111 true /* aSkipOwner */);
4112 if (contentToFocus) {
4113 NS_ADDREF(*aResultContent = contentToFocus);
4114 return NS_OK;
4118 // If startContent is a popover invoker, search the popover scope.
4119 if (!aSkipPopover) {
4120 if (InvokerForPopoverShowingState(startContent)) {
4121 if (aForward) {
4122 RefPtr<nsIContent> popover =
4123 startContent->GetEffectivePopoverTargetElement();
4124 nsIContent* contentToFocus = GetNextTabbableContentInScope(
4125 popover, popover, aOriginalStartContent, aForward, 1,
4126 aIgnoreTabIndex, aForDocumentNavigation, aNavigateByKey,
4127 true /* aSkipOwner */);
4128 if (contentToFocus) {
4129 NS_ADDREF(*aResultContent = contentToFocus);
4130 return NS_OK;
4136 // If startContent is in a scope owned by Shadow DOM search from scope
4137 // including startContent
4138 if (nsCOMPtr<nsIContent> owner = FindScopeOwner(startContent)) {
4139 nsIContent* contentToFocus = GetNextTabbableContentInAncestorScopes(
4140 owner, startContent /* inout */, aOriginalStartContent, aForward,
4141 &aCurrentTabIndex, &aIgnoreTabIndex, aForDocumentNavigation,
4142 aNavigateByKey);
4143 if (contentToFocus) {
4144 NS_ADDREF(*aResultContent = contentToFocus);
4145 return NS_OK;
4149 // If we reach here, it means no next tabbable content in shadow DOM.
4150 // We need to continue searching in light DOM, starting at the top level
4151 // shadow host in light DOM (updated startContent) and its tabindex
4152 // (updated aCurrentTabIndex).
4153 MOZ_ASSERT(!FindScopeOwner(startContent),
4154 "startContent should not be owned by Shadow DOM at this point");
4156 nsPresContext* presContext = aPresShell->GetPresContext();
4158 bool getNextFrame = true;
4159 nsCOMPtr<nsIContent> iterStartContent = startContent;
4160 nsIContent* topLevelScopeStartContent = startContent;
4161 // Iterate tab index to find corresponding contents
4162 while (1) {
4163 nsIFrame* frame = iterStartContent->GetPrimaryFrame();
4164 // if there is no frame, look for another content node that has a frame
4165 while (!frame) {
4166 // if the root content doesn't have a frame, just return
4167 if (iterStartContent == aRootContent) {
4168 return NS_OK;
4171 // look for the next or previous content node in tree order
4172 iterStartContent = aForward ? iterStartContent->GetNextNode()
4173 : iterStartContent->GetPreviousContent();
4174 if (!iterStartContent) {
4175 break;
4178 frame = iterStartContent->GetPrimaryFrame();
4179 // Host without frame, enter its scope.
4180 if (!frame && iterStartContent->GetShadowRoot()) {
4181 int32_t tabIndex = HostOrSlotTabIndexValue(iterStartContent);
4182 if (tabIndex >= 0 &&
4183 (aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) {
4184 nsIContent* contentToFocus = GetNextTabbableContentInScope(
4185 iterStartContent, iterStartContent, aOriginalStartContent,
4186 aForward, aForward ? 1 : 0, aIgnoreTabIndex,
4187 aForDocumentNavigation, aNavigateByKey, true /* aSkipOwner */);
4188 if (contentToFocus) {
4189 NS_ADDREF(*aResultContent = contentToFocus);
4190 return NS_OK;
4194 // we've already skipped over the initial focused content, so we
4195 // don't want to traverse frames.
4196 getNextFrame = false;
4199 Maybe<nsFrameIterator> frameIterator;
4200 if (frame) {
4201 // For tab navigation, pass false for aSkipPopupChecks so that we don't
4202 // iterate into or out of a popup. For document naviation pass true to
4203 // ignore these boundaries.
4204 frameIterator.emplace(presContext, frame, nsFrameIterator::Type::PreOrder,
4205 false, // aVisual
4206 false, // aLockInScrollView
4207 true, // aFollowOOFs
4208 aForDocumentNavigation // aSkipPopupChecks
4210 MOZ_ASSERT(frameIterator);
4212 if (iterStartContent == aRootContent) {
4213 if (!aForward) {
4214 frameIterator->Last();
4215 } else if (aRootContent->IsFocusableWithoutStyle()) {
4216 frameIterator->Next();
4218 frame = frameIterator->CurrentItem();
4219 } else if (getNextFrame &&
4220 (!iterStartContent ||
4221 !iterStartContent->IsHTMLElement(nsGkAtoms::area))) {
4222 // Need to do special check in case we're in an imagemap which has
4223 // multiple content nodes per frame, so don't skip over the starting
4224 // frame.
4225 frame = frameIterator->Traverse(aForward);
4229 nsIContent* oldTopLevelScopeOwner = nullptr;
4230 // Walk frames to find something tabbable matching aCurrentTabIndex
4231 while (frame) {
4232 // Try to find the topmost scope owner, since we want to skip the node
4233 // that is not owned by document in frame traversal.
4234 const nsCOMPtr<nsIContent> currentContent = frame->GetContent();
4235 if (currentTopLevelScopeOwner) {
4236 oldTopLevelScopeOwner = currentTopLevelScopeOwner;
4238 currentTopLevelScopeOwner = GetTopLevelScopeOwner(currentContent);
4240 // We handle popover case separately.
4241 if (currentTopLevelScopeOwner &&
4242 currentTopLevelScopeOwner == oldTopLevelScopeOwner &&
4243 !IsOpenPopoverWithInvoker(currentTopLevelScopeOwner)) {
4244 // We're within non-document scope, continue.
4245 do {
4246 if (aForward) {
4247 frameIterator->Next();
4248 } else {
4249 frameIterator->Prev();
4251 frame = frameIterator->CurrentItem();
4252 // For the usage of GetPrevContinuation, see the comment
4253 // at the end of while (frame) loop.
4254 } while (frame && frame->GetPrevContinuation());
4255 continue;
4258 // Stepping out popover scope.
4259 // For forward, search for the next tabbable content after invoker.
4260 // For backward, we should get back to the invoker.
4261 if (oldTopLevelScopeOwner &&
4262 IsOpenPopoverWithInvoker(oldTopLevelScopeOwner) &&
4263 currentTopLevelScopeOwner != oldTopLevelScopeOwner) {
4264 if (auto* popover = Element::FromNode(oldTopLevelScopeOwner)) {
4265 RefPtr<nsIContent> invokerContent =
4266 popover->GetPopoverData()->GetInvoker()->AsContent();
4267 if (aForward) {
4268 nsIFrame* frame = invokerContent->GetPrimaryFrame();
4269 int32_t tabIndex = frame->IsFocusable().mTabIndex;
4270 RefPtr<nsIContent> rootElement = invokerContent;
4271 if (auto* doc = invokerContent->GetComposedDoc()) {
4272 rootElement = doc->GetRootElement();
4274 if (tabIndex >= 0 &&
4275 (aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) {
4276 nsresult rv = GetNextTabbableContent(
4277 aPresShell, rootElement, nullptr, invokerContent, true,
4278 tabIndex, false, false, aNavigateByKey, true, aResultContent);
4279 if (NS_SUCCEEDED(rv) && *aResultContent) {
4280 return rv;
4283 } else if (invokerContent &&
4284 invokerContent->IsFocusableWithoutStyle()) {
4285 // FIXME(emilio): The check above should probably use
4286 // nsIFrame::IsFocusable, not IsFocusableWithoutStyle.
4287 invokerContent.forget(aResultContent);
4288 return NS_OK;
4293 if (!aForward) {
4294 if (InvokerForPopoverShowingState(currentContent)) {
4295 RefPtr<nsIContent> popover =
4296 currentContent->GetEffectivePopoverTargetElement();
4297 nsIContent* contentToFocus = GetNextTabbableContentInScope(
4298 popover, popover, aOriginalStartContent, aForward, 0,
4299 aIgnoreTabIndex, aForDocumentNavigation, aNavigateByKey,
4300 true /* aSkipOwner */);
4302 if (contentToFocus) {
4303 NS_ADDREF(*aResultContent = contentToFocus);
4304 return NS_OK;
4308 // For document navigation, check if this element is an open panel. Since
4309 // panels aren't focusable (tabIndex would be -1), we'll just assume that
4310 // for document navigation, the tabIndex is 0.
4311 if (aForDocumentNavigation && currentContent && (aCurrentTabIndex == 0) &&
4312 currentContent->IsXULElement(nsGkAtoms::panel)) {
4313 nsMenuPopupFrame* popupFrame = do_QueryFrame(frame);
4314 // Check if the panel is open. Closed panels are ignored since you can't
4315 // focus anything in them.
4316 if (popupFrame && popupFrame->IsOpen()) {
4317 // When moving backward, skip the popup we started in otherwise it
4318 // will be selected again.
4319 bool validPopup = true;
4320 if (!aForward) {
4321 nsIContent* content = topLevelScopeStartContent;
4322 while (content) {
4323 if (content == currentContent) {
4324 validPopup = false;
4325 break;
4328 content = content->GetParent();
4332 if (validPopup) {
4333 // Since a panel isn't focusable itself, find the first focusable
4334 // content within the popup. If there isn't any focusable content
4335 // in the popup, skip this popup and continue iterating through the
4336 // frames. We pass the panel itself (currentContent) as the starting
4337 // and root content, so that we only find content within the panel.
4338 // Note also that we pass false for aForDocumentNavigation since we
4339 // want to locate the first content, not the first document.
4340 nsresult rv = GetNextTabbableContent(
4341 aPresShell, currentContent, nullptr, currentContent, true, 1,
4342 false, false, aNavigateByKey, false, aResultContent);
4343 if (NS_SUCCEEDED(rv) && *aResultContent) {
4344 return rv;
4350 // As of now, 2018/04/12, sequential focus navigation is still
4351 // in the obsolete Shadow DOM specification.
4352 // http://w3c.github.io/webcomponents/spec/shadow/#sequential-focus-navigation
4353 // "if ELEMENT is focusable, a shadow host, or a slot element,
4354 // append ELEMENT to NAVIGATION-ORDER."
4355 // and later in "For each element ELEMENT in NAVIGATION-ORDER: "
4356 // hosts and slots are handled before other elements.
4357 if (currentTopLevelScopeOwner &&
4358 !IsOpenPopoverWithInvoker(currentTopLevelScopeOwner)) {
4359 bool focusableHostSlot;
4360 int32_t tabIndex = HostOrSlotTabIndexValue(currentTopLevelScopeOwner,
4361 &focusableHostSlot);
4362 // Host or slot itself isn't focusable or going backwards, enter its
4363 // scope.
4364 if ((!aForward || !focusableHostSlot) && tabIndex >= 0 &&
4365 (aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) {
4366 nsIContent* contentToFocus = GetNextTabbableContentInScope(
4367 currentTopLevelScopeOwner, currentTopLevelScopeOwner,
4368 aOriginalStartContent, aForward, aForward ? 1 : 0,
4369 aIgnoreTabIndex, aForDocumentNavigation, aNavigateByKey,
4370 true /* aSkipOwner */);
4371 if (contentToFocus) {
4372 NS_ADDREF(*aResultContent = contentToFocus);
4373 return NS_OK;
4375 // If we've wrapped around already, then carry on.
4376 if (aOriginalStartContent &&
4377 currentTopLevelScopeOwner ==
4378 GetTopLevelScopeOwner(aOriginalStartContent)) {
4379 // FIXME: Shouldn't this return null instead? aOriginalStartContent
4380 // isn't focusable after all.
4381 NS_ADDREF(*aResultContent = aOriginalStartContent);
4382 return NS_OK;
4385 // There is no next tabbable content in currentTopLevelScopeOwner's
4386 // scope. We should continue the loop in order to skip all contents that
4387 // is in currentTopLevelScopeOwner's scope.
4388 continue;
4391 MOZ_ASSERT(
4392 !GetTopLevelScopeOwner(currentContent) ||
4393 IsOpenPopoverWithInvoker(GetTopLevelScopeOwner(currentContent)),
4394 "currentContent should be in top-level-scope at this point unless "
4395 "for popover case");
4397 // TabIndex not set defaults to 0 for form elements, anchors and other
4398 // elements that are normally focusable. Tabindex defaults to -1
4399 // for elements that are not normally focusable.
4400 // The returned computed tabindex from IsFocusable() is as follows:
4401 // clang-format off
4402 // < 0 not tabbable at all
4403 // == 0 in normal tab order (last after positive tabindexed items)
4404 // > 0 can be tabbed to in the order specified by this value
4405 // clang-format on
4406 int32_t tabIndex = frame->IsFocusable().mTabIndex;
4408 LOGCONTENTNAVIGATION("Next Tabbable %s:", frame->GetContent());
4409 LOGFOCUSNAVIGATION(
4410 (" with tabindex: %d expected: %d", tabIndex, aCurrentTabIndex));
4412 if (tabIndex >= 0) {
4413 NS_ASSERTION(currentContent,
4414 "IsFocusable set a tabindex for a frame with no content");
4415 if (!aForDocumentNavigation &&
4416 currentContent->IsHTMLElement(nsGkAtoms::img) &&
4417 currentContent->AsElement()->HasAttr(nsGkAtoms::usemap)) {
4418 // This is an image with a map. Image map areas are not traversed by
4419 // nsFrameIterator so look for the next or previous area element.
4420 nsIContent* areaContent = GetNextTabbableMapArea(
4421 aForward, aCurrentTabIndex, currentContent->AsElement(),
4422 iterStartContent);
4423 if (areaContent) {
4424 NS_ADDREF(*aResultContent = areaContent);
4425 return NS_OK;
4427 } else if (aIgnoreTabIndex || aCurrentTabIndex == tabIndex) {
4428 // break out if we've wrapped around to the start again.
4429 if (aOriginalStartContent &&
4430 currentContent == aOriginalStartContent) {
4431 NS_ADDREF(*aResultContent = currentContent);
4432 return NS_OK;
4435 // If this is a remote child browser, call NavigateDocument to have
4436 // the child process continue the navigation. Return a special error
4437 // code to have the caller return early. If the child ends up not
4438 // being focusable in some way, the child process will call back
4439 // into document navigation again by calling MoveFocus.
4440 if (BrowserParent* remote = BrowserParent::GetFrom(currentContent)) {
4441 if (aNavigateByKey) {
4442 remote->NavigateByKey(aForward, aForDocumentNavigation);
4443 return NS_SUCCESS_DOM_NO_OPERATION;
4445 return NS_OK;
4448 // Same as above but for out-of-process iframes
4449 if (auto* bbc = BrowserBridgeChild::GetFrom(currentContent)) {
4450 if (aNavigateByKey) {
4451 bbc->NavigateByKey(aForward, aForDocumentNavigation);
4452 return NS_SUCCESS_DOM_NO_OPERATION;
4454 return NS_OK;
4457 // Next, for document navigation, check if this a non-remote child
4458 // document.
4459 bool checkSubDocument = true;
4460 if (aForDocumentNavigation &&
4461 TryDocumentNavigation(currentContent, &checkSubDocument,
4462 aResultContent)) {
4463 return NS_OK;
4466 if (checkSubDocument) {
4467 // found a node with a matching tab index. Check if it is a child
4468 // frame. If so, navigate into the child frame instead.
4469 if (TryToMoveFocusToSubDocument(
4470 currentContent, aOriginalStartContent, aForward,
4471 aForDocumentNavigation, aNavigateByKey, aResultContent)) {
4472 MOZ_ASSERT(*aResultContent);
4473 return NS_OK;
4475 // otherwise, use this as the next content node to tab to, unless
4476 // this was the element we started on. This would happen for
4477 // instance on an element with child frames, where frame navigation
4478 // could return the original element again. In that case, just skip
4479 // it. Also, if the next content node is the root content, then
4480 // return it. This latter case would happen only if someone made a
4481 // popup focusable.
4482 else if (currentContent == aRootContent ||
4483 currentContent != startContent) {
4484 NS_ADDREF(*aResultContent = currentContent);
4485 return NS_OK;
4489 } else if (aOriginalStartContent &&
4490 currentContent == aOriginalStartContent) {
4491 // not focusable, so return if we have wrapped around to the original
4492 // content. This is necessary in case the original starting content was
4493 // not focusable.
4495 // FIXME: Shouldn't this return null instead? currentContent isn't
4496 // focusable after all.
4497 NS_ADDREF(*aResultContent = currentContent);
4498 return NS_OK;
4501 // Move to the next or previous frame, but ignore continuation frames
4502 // since only the first frame should be involved in focusability.
4503 // Otherwise, a loop will occur in the following example:
4504 // <span tabindex="1">...<a/><a/>...</span>
4505 // where the text wraps onto multiple lines. Tabbing from the second
4506 // link can find one of the span's continuation frames between the link
4507 // and the end of the span, and the span would end up getting focused
4508 // again.
4509 do {
4510 if (aForward) {
4511 frameIterator->Next();
4512 } else {
4513 frameIterator->Prev();
4515 frame = frameIterator->CurrentItem();
4516 } while (frame && frame->GetPrevContinuation());
4519 // If already at lowest priority tab (0), end search completely.
4520 // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
4521 if (aCurrentTabIndex == (aForward ? 0 : 1)) {
4522 // if going backwards, the canvas should be focused once the beginning
4523 // has been reached, so get the root element.
4524 if (!aForward) {
4525 nsCOMPtr<nsPIDOMWindowOuter> window = GetCurrentWindow(aRootContent);
4526 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
4528 RefPtr<Element> docRoot = GetRootForFocus(
4529 window, aRootContent->GetComposedDoc(), false, true);
4530 FocusFirst(docRoot, aResultContent);
4532 break;
4535 // continue looking for next highest priority tabindex
4536 aCurrentTabIndex =
4537 GetNextTabIndex(aRootContent, aCurrentTabIndex, aForward);
4538 startContent = iterStartContent = aRootContent;
4539 currentTopLevelScopeOwner = GetTopLevelScopeOwner(startContent);
4542 return NS_OK;
4545 bool nsFocusManager::TryDocumentNavigation(nsIContent* aCurrentContent,
4546 bool* aCheckSubDocument,
4547 nsIContent** aResultContent) {
4548 *aCheckSubDocument = true;
4549 if (RefPtr<Element> rootElementForChildDocument =
4550 GetRootForChildDocument(aCurrentContent)) {
4551 // If GetRootForChildDocument returned something then call
4552 // FocusFirst to find the root or first element to focus within
4553 // the child document. If this is a frameset though, skip this and
4554 // fall through to normal tab navigation to iterate into
4555 // the frameset's frames and locate the first focusable frame.
4556 if (!rootElementForChildDocument->IsHTMLElement(nsGkAtoms::frameset)) {
4557 *aCheckSubDocument = false;
4558 Unused << FocusFirst(rootElementForChildDocument, aResultContent);
4559 return *aResultContent != nullptr;
4561 } else {
4562 // Set aCheckSubDocument to false, as this was neither a frame
4563 // type element or a child document that was focusable.
4564 *aCheckSubDocument = false;
4567 return false;
4570 bool nsFocusManager::TryToMoveFocusToSubDocument(
4571 nsIContent* aCurrentContent, nsIContent* aOriginalStartContent,
4572 bool aForward, bool aForDocumentNavigation, bool aNavigateByKey,
4573 nsIContent** aResultContent) {
4574 Document* doc = aCurrentContent->GetComposedDoc();
4575 NS_ASSERTION(doc, "content not in document");
4576 Document* subdoc = doc->GetSubDocumentFor(aCurrentContent);
4577 if (subdoc && !subdoc->EventHandlingSuppressed()) {
4578 if (aForward) {
4579 // When tabbing forward into a frame, return the root
4580 // frame so that the canvas becomes focused.
4581 if (nsCOMPtr<nsPIDOMWindowOuter> subframe = subdoc->GetWindow()) {
4582 *aResultContent = GetRootForFocus(subframe, subdoc, false, true);
4583 if (*aResultContent) {
4584 NS_ADDREF(*aResultContent);
4585 return true;
4589 if (RefPtr<Element> rootElement = subdoc->GetRootElement()) {
4590 if (RefPtr<PresShell> subPresShell = subdoc->GetPresShell()) {
4591 nsresult rv = GetNextTabbableContent(
4592 subPresShell, rootElement, aOriginalStartContent, rootElement,
4593 aForward, (aForward ? 1 : 0), false, aForDocumentNavigation,
4594 aNavigateByKey, false, aResultContent);
4595 NS_ENSURE_SUCCESS(rv, false);
4596 if (*aResultContent) {
4597 return true;
4602 return false;
4605 nsIContent* nsFocusManager::GetNextTabbableMapArea(bool aForward,
4606 int32_t aCurrentTabIndex,
4607 Element* aImageContent,
4608 nsIContent* aStartContent) {
4609 if (aImageContent->IsInComposedDoc()) {
4610 HTMLImageElement* imgElement = HTMLImageElement::FromNode(aImageContent);
4611 // The caller should check the element type, so we can assert here.
4612 MOZ_ASSERT(imgElement);
4614 nsCOMPtr<nsIContent> mapContent = imgElement->FindImageMap();
4615 if (!mapContent) {
4616 return nullptr;
4618 // First see if the the start content is in this map
4619 Maybe<uint32_t> indexOfStartContent =
4620 mapContent->ComputeIndexOf(aStartContent);
4621 nsIContent* scanStartContent;
4622 Focusable focusable;
4623 if (indexOfStartContent.isNothing() ||
4624 ((focusable = aStartContent->IsFocusableWithoutStyle()) &&
4625 focusable.mTabIndex != aCurrentTabIndex)) {
4626 // If aStartContent is in this map we must start iterating past it.
4627 // We skip the case where aStartContent has tabindex == aStartContent
4628 // since the next tab ordered element might be before it
4629 // (or after for backwards) in the child list.
4630 scanStartContent =
4631 aForward ? mapContent->GetFirstChild() : mapContent->GetLastChild();
4632 } else {
4633 scanStartContent = aForward ? aStartContent->GetNextSibling()
4634 : aStartContent->GetPreviousSibling();
4637 for (nsCOMPtr<nsIContent> areaContent = scanStartContent; areaContent;
4638 areaContent = aForward ? areaContent->GetNextSibling()
4639 : areaContent->GetPreviousSibling()) {
4640 focusable = areaContent->IsFocusableWithoutStyle();
4641 if (focusable && focusable.mTabIndex == aCurrentTabIndex) {
4642 return areaContent;
4647 return nullptr;
4650 int32_t nsFocusManager::GetNextTabIndex(nsIContent* aParent,
4651 int32_t aCurrentTabIndex,
4652 bool aForward) {
4653 int32_t tabIndex, childTabIndex;
4654 StyleChildrenIterator iter(aParent);
4656 if (aForward) {
4657 tabIndex = 0;
4658 for (nsIContent* child = iter.GetNextChild(); child;
4659 child = iter.GetNextChild()) {
4660 // Skip child's descendants if child is a shadow host or slot, as they are
4661 // in the focus navigation scope owned by child's shadow root
4662 if (!IsHostOrSlot(child)) {
4663 childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
4664 if (childTabIndex > aCurrentTabIndex && childTabIndex != tabIndex) {
4665 tabIndex = (tabIndex == 0 || childTabIndex < tabIndex) ? childTabIndex
4666 : tabIndex;
4670 nsAutoString tabIndexStr;
4671 if (child->IsElement()) {
4672 child->AsElement()->GetAttr(nsGkAtoms::tabindex, tabIndexStr);
4674 nsresult ec;
4675 int32_t val = tabIndexStr.ToInteger(&ec);
4676 if (NS_SUCCEEDED(ec) && val > aCurrentTabIndex && val != tabIndex) {
4677 tabIndex = (tabIndex == 0 || val < tabIndex) ? val : tabIndex;
4680 } else { /* !aForward */
4681 tabIndex = 1;
4682 for (nsIContent* child = iter.GetNextChild(); child;
4683 child = iter.GetNextChild()) {
4684 // Skip child's descendants if child is a shadow host or slot, as they are
4685 // in the focus navigation scope owned by child's shadow root
4686 if (!IsHostOrSlot(child)) {
4687 childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
4688 if ((aCurrentTabIndex == 0 && childTabIndex > tabIndex) ||
4689 (childTabIndex < aCurrentTabIndex && childTabIndex > tabIndex)) {
4690 tabIndex = childTabIndex;
4694 nsAutoString tabIndexStr;
4695 if (child->IsElement()) {
4696 child->AsElement()->GetAttr(nsGkAtoms::tabindex, tabIndexStr);
4698 nsresult ec;
4699 int32_t val = tabIndexStr.ToInteger(&ec);
4700 if (NS_SUCCEEDED(ec)) {
4701 if ((aCurrentTabIndex == 0 && val > tabIndex) ||
4702 (val < aCurrentTabIndex && val > tabIndex)) {
4703 tabIndex = val;
4709 return tabIndex;
4712 nsresult nsFocusManager::FocusFirst(Element* aRootElement,
4713 nsIContent** aNextContent) {
4714 if (!aRootElement) {
4715 return NS_OK;
4718 Document* doc = aRootElement->GetComposedDoc();
4719 if (doc) {
4720 if (nsContentUtils::IsChromeDoc(doc)) {
4721 // If the redirectdocumentfocus attribute is set, redirect the focus to a
4722 // specific element. This is primarily used to retarget the focus to the
4723 // urlbar during document navigation.
4724 nsAutoString retarget;
4726 if (aRootElement->GetAttr(nsGkAtoms::retargetdocumentfocus, retarget)) {
4727 RefPtr<Element> element = doc->GetElementById(retarget);
4728 nsCOMPtr<nsIContent> retargetElement =
4729 FlushAndCheckIfFocusable(element, 0);
4730 if (retargetElement) {
4731 retargetElement.forget(aNextContent);
4732 return NS_OK;
4737 nsCOMPtr<nsIDocShell> docShell = doc->GetDocShell();
4738 if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
4739 // If the found content is in a chrome shell, navigate forward one
4740 // tabbable item so that the first item is focused. Note that we
4741 // always go forward and not back here.
4742 if (RefPtr<PresShell> presShell = doc->GetPresShell()) {
4743 return GetNextTabbableContent(presShell, aRootElement, nullptr,
4744 aRootElement, true, 1, false, false, true,
4745 false, aNextContent);
4750 NS_ADDREF(*aNextContent = aRootElement);
4751 return NS_OK;
4754 Element* nsFocusManager::GetRootForFocus(nsPIDOMWindowOuter* aWindow,
4755 Document* aDocument,
4756 bool aForDocumentNavigation,
4757 bool aCheckVisibility) {
4758 if (!aForDocumentNavigation) {
4759 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
4760 if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
4761 return nullptr;
4765 if (aCheckVisibility && !IsWindowVisible(aWindow)) return nullptr;
4767 // If the body is contenteditable, use the editor's root element rather than
4768 // the actual root element.
4769 RefPtr<Element> rootElement =
4770 nsLayoutUtils::GetEditableRootContentByContentEditable(aDocument);
4771 if (!rootElement || !rootElement->GetPrimaryFrame()) {
4772 rootElement = aDocument->GetRootElement();
4773 if (!rootElement) {
4774 return nullptr;
4778 if (aCheckVisibility && !rootElement->GetPrimaryFrame()) {
4779 return nullptr;
4782 // Finally, check if this is a frameset
4783 if (aDocument && aDocument->IsHTMLOrXHTML()) {
4784 Element* htmlChild = aDocument->GetHtmlChildElement(nsGkAtoms::frameset);
4785 if (htmlChild) {
4786 // In document navigation mode, return the frameset so that navigation
4787 // descends into the child frames.
4788 return aForDocumentNavigation ? htmlChild : nullptr;
4792 return rootElement;
4795 Element* nsFocusManager::GetRootForChildDocument(nsIContent* aContent) {
4796 // Check for elements that represent child documents, that is, browsers,
4797 // editors or frames from a frameset. We don't include iframes since we
4798 // consider them to be an integral part of the same window or page.
4799 if (!aContent || !(aContent->IsXULElement(nsGkAtoms::browser) ||
4800 aContent->IsXULElement(nsGkAtoms::editor) ||
4801 aContent->IsHTMLElement(nsGkAtoms::frame))) {
4802 return nullptr;
4805 Document* doc = aContent->GetComposedDoc();
4806 if (!doc) {
4807 return nullptr;
4810 Document* subdoc = doc->GetSubDocumentFor(aContent);
4811 if (!subdoc || subdoc->EventHandlingSuppressed()) {
4812 return nullptr;
4815 nsCOMPtr<nsPIDOMWindowOuter> window = subdoc->GetWindow();
4816 return GetRootForFocus(window, subdoc, true, true);
4819 static bool IsLink(nsIContent* aContent) {
4820 return aContent->IsElement() && aContent->AsElement()->IsLink();
4823 void nsFocusManager::GetFocusInSelection(nsPIDOMWindowOuter* aWindow,
4824 nsIContent* aStartSelection,
4825 nsIContent* aEndSelection,
4826 nsIContent** aFocusedContent) {
4827 *aFocusedContent = nullptr;
4829 nsCOMPtr<nsIContent> testContent = aStartSelection;
4830 nsCOMPtr<nsIContent> nextTestContent = aEndSelection;
4832 nsCOMPtr<nsIContent> currentFocus = aWindow->GetFocusedElement();
4834 // We now have the correct start node in selectionContent!
4835 // Search for focusable elements, starting with selectionContent
4837 // Method #1: Keep going up while we look - an ancestor might be focusable
4838 // We could end the loop earlier, such as when we're no longer
4839 // in the same frame, by comparing selectionContent->GetPrimaryFrame()
4840 // with a variable holding the starting selectionContent
4841 while (testContent) {
4842 // Keep testing while selectionContent is equal to something,
4843 // eventually we'll run out of ancestors
4845 if (testContent == currentFocus || IsLink(testContent)) {
4846 testContent.forget(aFocusedContent);
4847 return;
4850 // Get the parent
4851 testContent = testContent->GetParent();
4853 if (!testContent) {
4854 // We run this loop again, checking the ancestor chain of the selection's
4855 // end point
4856 testContent = nextTestContent;
4857 nextTestContent = nullptr;
4861 // We couldn't find an anchor that was an ancestor of the selection start
4862 // Method #2: look for anchor in selection's primary range (depth first
4863 // search)
4865 nsCOMPtr<nsIContent> selectionNode = aStartSelection;
4866 nsCOMPtr<nsIContent> endSelectionNode = aEndSelection;
4867 nsCOMPtr<nsIContent> testNode;
4869 do {
4870 testContent = selectionNode;
4872 // We're looking for any focusable link that could be part of the
4873 // main document's selection.
4874 if (testContent == currentFocus || IsLink(testContent)) {
4875 testContent.forget(aFocusedContent);
4876 return;
4879 nsIContent* testNode = selectionNode->GetFirstChild();
4880 if (testNode) {
4881 selectionNode = testNode;
4882 continue;
4885 if (selectionNode == endSelectionNode) {
4886 break;
4888 testNode = selectionNode->GetNextSibling();
4889 if (testNode) {
4890 selectionNode = testNode;
4891 continue;
4894 do {
4895 // GetParent is OK here, instead of GetParentNode, because the only case
4896 // where the latter returns something different from the former is when
4897 // GetParentNode is the document. But in that case we would simply get
4898 // null for selectionNode when setting it to testNode->GetNextSibling()
4899 // (because a document has no next sibling). And then the next iteration
4900 // of this loop would get null for GetParentNode anyway, and break out of
4901 // all the loops.
4902 testNode = selectionNode->GetParent();
4903 if (!testNode || testNode == endSelectionNode) {
4904 selectionNode = nullptr;
4905 break;
4907 selectionNode = testNode->GetNextSibling();
4908 if (selectionNode) {
4909 break;
4911 selectionNode = testNode;
4912 } while (true);
4913 } while (selectionNode && selectionNode != endSelectionNode);
4916 static void MaybeUnlockPointer(BrowsingContext* aCurrentFocusedContext) {
4917 if (!PointerLockManager::IsInLockContext(aCurrentFocusedContext)) {
4918 PointerLockManager::Unlock();
4922 class PointerUnlocker : public Runnable {
4923 public:
4924 PointerUnlocker() : mozilla::Runnable("PointerUnlocker") {
4925 MOZ_ASSERT(XRE_IsParentProcess());
4926 MOZ_ASSERT(!PointerUnlocker::sActiveUnlocker);
4927 PointerUnlocker::sActiveUnlocker = this;
4930 ~PointerUnlocker() {
4931 if (PointerUnlocker::sActiveUnlocker == this) {
4932 PointerUnlocker::sActiveUnlocker = nullptr;
4936 NS_IMETHOD Run() override {
4937 if (PointerUnlocker::sActiveUnlocker == this) {
4938 PointerUnlocker::sActiveUnlocker = nullptr;
4940 NS_ENSURE_STATE(nsFocusManager::GetFocusManager());
4941 nsPIDOMWindowOuter* focused =
4942 nsFocusManager::GetFocusManager()->GetFocusedWindow();
4943 MaybeUnlockPointer(focused ? focused->GetBrowsingContext() : nullptr);
4944 return NS_OK;
4947 static PointerUnlocker* sActiveUnlocker;
4950 PointerUnlocker* PointerUnlocker::sActiveUnlocker = nullptr;
4952 void nsFocusManager::SetFocusedBrowsingContext(BrowsingContext* aContext,
4953 uint64_t aActionId) {
4954 if (XRE_IsParentProcess()) {
4955 return;
4957 MOZ_ASSERT(!ActionIdComparableAndLower(
4958 aActionId, mActionIdForFocusedBrowsingContextInContent));
4959 mFocusedBrowsingContextInContent = aContext;
4960 mActionIdForFocusedBrowsingContextInContent = aActionId;
4961 if (aContext) {
4962 // We don't send the unset but instead expect the set from
4963 // elsewhere to take care of it. XXX Is that bad?
4964 MOZ_ASSERT(aContext->IsInProcess());
4965 mozilla::dom::ContentChild* contentChild =
4966 mozilla::dom::ContentChild::GetSingleton();
4967 MOZ_ASSERT(contentChild);
4968 contentChild->SendSetFocusedBrowsingContext(aContext, aActionId);
4972 void nsFocusManager::SetFocusedBrowsingContextFromOtherProcess(
4973 BrowsingContext* aContext, uint64_t aActionId) {
4974 MOZ_ASSERT(!XRE_IsParentProcess());
4975 MOZ_ASSERT(aContext);
4976 if (ActionIdComparableAndLower(aActionId,
4977 mActionIdForFocusedBrowsingContextInContent)) {
4978 // Unclear if this ever happens.
4979 LOGFOCUS(
4980 ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
4981 "focused from another process due to stale action id %" PRIu64 ".",
4982 aContext, aActionId));
4983 return;
4985 if (aContext->IsInProcess()) {
4986 // This message has been in transit for long enough that
4987 // the process association of aContext has changed since
4988 // the other content process sent the message, because
4989 // an iframe in that process became an out-of-process
4990 // iframe while the IPC broadcast that we're receiving
4991 // was in-flight. Let's just ignore this.
4992 LOGFOCUS(
4993 ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
4994 "focused from another process, actionid: %" PRIu64 ".",
4995 aContext, aActionId));
4996 return;
4998 mFocusedBrowsingContextInContent = aContext;
4999 mActionIdForFocusedBrowsingContextInContent = aActionId;
5000 mFocusedElement = nullptr;
5001 mFocusedWindow = nullptr;
5004 bool nsFocusManager::SetFocusedBrowsingContextInChrome(
5005 mozilla::dom::BrowsingContext* aContext, uint64_t aActionId) {
5006 MOZ_ASSERT(aActionId);
5007 if (ProcessPendingFocusedBrowsingContextActionId(aActionId)) {
5008 MOZ_DIAGNOSTIC_ASSERT(!ActionIdComparableAndLower(
5009 aActionId, mActionIdForFocusedBrowsingContextInChrome));
5010 mFocusedBrowsingContextInChrome = aContext;
5011 mActionIdForFocusedBrowsingContextInChrome = aActionId;
5012 return true;
5014 return false;
5017 BrowsingContext* nsFocusManager::GetFocusedBrowsingContextInChrome() {
5018 return mFocusedBrowsingContextInChrome;
5021 void nsFocusManager::BrowsingContextDetached(BrowsingContext* aContext) {
5022 if (mFocusedBrowsingContextInChrome == aContext) {
5023 mFocusedBrowsingContextInChrome = nullptr;
5024 // Deliberately not adjusting the corresponding action id, because
5025 // we don't want changes from the past to take effect.
5027 if (mActiveBrowsingContextInChrome == aContext) {
5028 mActiveBrowsingContextInChrome = nullptr;
5029 // Deliberately not adjusting the corresponding action id, because
5030 // we don't want changes from the past to take effect.
5034 void nsFocusManager::SetActiveBrowsingContextInContent(
5035 mozilla::dom::BrowsingContext* aContext, uint64_t aActionId) {
5036 MOZ_ASSERT(!XRE_IsParentProcess());
5037 MOZ_ASSERT(!aContext || aContext->IsInProcess());
5038 mozilla::dom::ContentChild* contentChild =
5039 mozilla::dom::ContentChild::GetSingleton();
5040 MOZ_ASSERT(contentChild);
5042 if (ActionIdComparableAndLower(aActionId,
5043 mActionIdForActiveBrowsingContextInContent)) {
5044 LOGFOCUS(
5045 ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
5046 "the active browsing context due to a stale action id %" PRIu64 ".",
5047 aContext, aActionId));
5048 return;
5051 if (aContext != mActiveBrowsingContextInContent) {
5052 if (aContext) {
5053 contentChild->SendSetActiveBrowsingContext(aContext, aActionId);
5054 } else if (mActiveBrowsingContextInContent) {
5055 // We want to sync this over only if this isn't happening
5056 // due to the active BrowsingContext switching processes,
5057 // in which case the BrowserChild has already marked itself
5058 // as destroying.
5059 nsPIDOMWindowOuter* outer =
5060 mActiveBrowsingContextInContent->GetDOMWindow();
5061 if (outer) {
5062 nsPIDOMWindowInner* inner = outer->GetCurrentInnerWindow();
5063 if (inner) {
5064 WindowGlobalChild* globalChild = inner->GetWindowGlobalChild();
5065 if (globalChild) {
5066 RefPtr<BrowserChild> browserChild = globalChild->GetBrowserChild();
5067 if (browserChild && !browserChild->IsDestroyed()) {
5068 contentChild->SendUnsetActiveBrowsingContext(
5069 mActiveBrowsingContextInContent, aActionId);
5076 mActiveBrowsingContextInContentSetFromOtherProcess = false;
5077 mActiveBrowsingContextInContent = aContext;
5078 mActionIdForActiveBrowsingContextInContent = aActionId;
5079 MaybeUnlockPointer(aContext);
5082 void nsFocusManager::SetActiveBrowsingContextFromOtherProcess(
5083 BrowsingContext* aContext, uint64_t aActionId) {
5084 MOZ_ASSERT(!XRE_IsParentProcess());
5085 MOZ_ASSERT(aContext);
5086 if (ActionIdComparableAndLower(aActionId,
5087 mActionIdForActiveBrowsingContextInContent)) {
5088 LOGFOCUS(
5089 ("Ignored an attempt to set active BrowsingContext [%p] from "
5090 "another process due to a stale action id %" PRIu64 ".",
5091 aContext, aActionId));
5092 return;
5094 if (aContext->IsInProcess()) {
5095 // This message has been in transit for long enough that
5096 // the process association of aContext has changed since
5097 // the other content process sent the message, because
5098 // an iframe in that process became an out-of-process
5099 // iframe while the IPC broadcast that we're receiving
5100 // was in-flight. Let's just ignore this.
5101 LOGFOCUS(
5102 ("Ignored an attempt to set an in-process BrowsingContext [%p] as "
5103 "active from another process. actionid: %" PRIu64,
5104 aContext, aActionId));
5105 return;
5107 mActiveBrowsingContextInContentSetFromOtherProcess = true;
5108 mActiveBrowsingContextInContent = aContext;
5109 mActionIdForActiveBrowsingContextInContent = aActionId;
5110 MaybeUnlockPointer(aContext);
5113 void nsFocusManager::UnsetActiveBrowsingContextFromOtherProcess(
5114 BrowsingContext* aContext, uint64_t aActionId) {
5115 MOZ_ASSERT(!XRE_IsParentProcess());
5116 MOZ_ASSERT(aContext);
5117 if (ActionIdComparableAndLower(aActionId,
5118 mActionIdForActiveBrowsingContextInContent)) {
5119 LOGFOCUS(
5120 ("Ignored an attempt to unset the active BrowsingContext [%p] from "
5121 "another process due to stale action id: %" PRIu64 ".",
5122 aContext, aActionId));
5123 return;
5125 if (mActiveBrowsingContextInContent == aContext) {
5126 mActiveBrowsingContextInContent = nullptr;
5127 mActionIdForActiveBrowsingContextInContent = aActionId;
5128 MaybeUnlockPointer(nullptr);
5129 } else {
5130 LOGFOCUS(
5131 ("Ignored an attempt to unset the active BrowsingContext [%p] from "
5132 "another process. actionid: %" PRIu64,
5133 aContext, aActionId));
5137 void nsFocusManager::ReviseActiveBrowsingContext(
5138 uint64_t aOldActionId, mozilla::dom::BrowsingContext* aContext,
5139 uint64_t aNewActionId) {
5140 MOZ_ASSERT(XRE_IsContentProcess());
5141 if (mActionIdForActiveBrowsingContextInContent == aOldActionId) {
5142 LOGFOCUS(("Revising the active BrowsingContext [%p]. old actionid: %" PRIu64
5143 ", new "
5144 "actionid: %" PRIu64,
5145 aContext, aOldActionId, aNewActionId));
5146 mActiveBrowsingContextInContent = aContext;
5147 mActionIdForActiveBrowsingContextInContent = aNewActionId;
5148 } else {
5149 LOGFOCUS(
5150 ("Ignored a stale attempt to revise the active BrowsingContext [%p]. "
5151 "old actionid: %" PRIu64 ", new actionid: %" PRIu64,
5152 aContext, aOldActionId, aNewActionId));
5156 void nsFocusManager::ReviseFocusedBrowsingContext(
5157 uint64_t aOldActionId, mozilla::dom::BrowsingContext* aContext,
5158 uint64_t aNewActionId) {
5159 MOZ_ASSERT(XRE_IsContentProcess());
5160 if (mActionIdForFocusedBrowsingContextInContent == aOldActionId) {
5161 LOGFOCUS(
5162 ("Revising the focused BrowsingContext [%p]. old actionid: %" PRIu64
5163 ", new "
5164 "actionid: %" PRIu64,
5165 aContext, aOldActionId, aNewActionId));
5166 mFocusedBrowsingContextInContent = aContext;
5167 mActionIdForFocusedBrowsingContextInContent = aNewActionId;
5168 mFocusedElement = nullptr;
5169 } else {
5170 LOGFOCUS(
5171 ("Ignored a stale attempt to revise the focused BrowsingContext [%p]. "
5172 "old actionid: %" PRIu64 ", new actionid: %" PRIu64,
5173 aContext, aOldActionId, aNewActionId));
5177 bool nsFocusManager::SetActiveBrowsingContextInChrome(
5178 mozilla::dom::BrowsingContext* aContext, uint64_t aActionId) {
5179 MOZ_ASSERT(aActionId);
5180 if (ProcessPendingActiveBrowsingContextActionId(aActionId, aContext)) {
5181 MOZ_DIAGNOSTIC_ASSERT(!ActionIdComparableAndLower(
5182 aActionId, mActionIdForActiveBrowsingContextInChrome));
5183 mActiveBrowsingContextInChrome = aContext;
5184 mActionIdForActiveBrowsingContextInChrome = aActionId;
5185 return true;
5187 return false;
5190 uint64_t nsFocusManager::GetActionIdForActiveBrowsingContextInChrome() const {
5191 return mActionIdForActiveBrowsingContextInChrome;
5194 uint64_t nsFocusManager::GetActionIdForFocusedBrowsingContextInChrome() const {
5195 return mActionIdForFocusedBrowsingContextInChrome;
5198 BrowsingContext* nsFocusManager::GetActiveBrowsingContextInChrome() {
5199 return mActiveBrowsingContextInChrome;
5202 void nsFocusManager::InsertNewFocusActionId(uint64_t aActionId) {
5203 LOGFOCUS(("InsertNewFocusActionId %" PRIu64, aActionId));
5204 MOZ_ASSERT(XRE_IsParentProcess());
5205 MOZ_ASSERT(!mPendingActiveBrowsingContextActions.Contains(aActionId));
5206 mPendingActiveBrowsingContextActions.AppendElement(aActionId);
5207 MOZ_ASSERT(!mPendingFocusedBrowsingContextActions.Contains(aActionId));
5208 mPendingFocusedBrowsingContextActions.AppendElement(aActionId);
5211 static void RemoveContentInitiatedActionsUntil(
5212 nsTArray<uint64_t>& aPendingActions,
5213 nsTArray<uint64_t>::index_type aUntil) {
5214 nsTArray<uint64_t>::index_type i = 0;
5215 while (i < aUntil) {
5216 auto [actionProc, actionId] =
5217 nsContentUtils::SplitProcessSpecificId(aPendingActions[i]);
5218 Unused << actionId;
5219 if (actionProc) {
5220 aPendingActions.RemoveElementAt(i);
5221 --aUntil;
5222 continue;
5224 ++i;
5228 bool nsFocusManager::ProcessPendingActiveBrowsingContextActionId(
5229 uint64_t aActionId, bool aSettingToNonNull) {
5230 MOZ_ASSERT(XRE_IsParentProcess());
5231 auto index = mPendingActiveBrowsingContextActions.IndexOf(aActionId);
5232 if (index == nsTArray<uint64_t>::NoIndex) {
5233 return false;
5235 // When aSettingToNonNull is true, we need to remove one more
5236 // element to remove the action id itself in addition to
5237 // removing the older ones.
5238 if (aSettingToNonNull) {
5239 index++;
5241 auto [actionProc, actionId] =
5242 nsContentUtils::SplitProcessSpecificId(aActionId);
5243 Unused << actionId;
5244 if (actionProc) {
5245 // Action from content: We allow parent-initiated actions
5246 // to take precedence over content-initiated ones, so we
5247 // remove only prior content-initiated actions.
5248 RemoveContentInitiatedActionsUntil(mPendingActiveBrowsingContextActions,
5249 index);
5250 } else {
5251 // Action from chrome
5252 mPendingActiveBrowsingContextActions.RemoveElementsAt(0, index);
5254 return true;
5257 bool nsFocusManager::ProcessPendingFocusedBrowsingContextActionId(
5258 uint64_t aActionId) {
5259 MOZ_ASSERT(XRE_IsParentProcess());
5260 auto index = mPendingFocusedBrowsingContextActions.IndexOf(aActionId);
5261 if (index == nsTArray<uint64_t>::NoIndex) {
5262 return false;
5265 auto [actionProc, actionId] =
5266 nsContentUtils::SplitProcessSpecificId(aActionId);
5267 Unused << actionId;
5268 if (actionProc) {
5269 // Action from content: We allow parent-initiated actions
5270 // to take precedence over content-initiated ones, so we
5271 // remove only prior content-initiated actions.
5272 RemoveContentInitiatedActionsUntil(mPendingFocusedBrowsingContextActions,
5273 index);
5274 } else {
5275 // Action from chrome
5276 mPendingFocusedBrowsingContextActions.RemoveElementsAt(0, index);
5278 return true;
5281 // static
5282 uint64_t nsFocusManager::GenerateFocusActionId() {
5283 uint64_t id =
5284 nsContentUtils::GenerateProcessSpecificId(++sFocusActionCounter);
5285 if (XRE_IsParentProcess()) {
5286 nsFocusManager* fm = GetFocusManager();
5287 if (fm) {
5288 fm->InsertNewFocusActionId(id);
5290 } else {
5291 mozilla::dom::ContentChild* contentChild =
5292 mozilla::dom::ContentChild::GetSingleton();
5293 MOZ_ASSERT(contentChild);
5294 contentChild->SendInsertNewFocusActionId(id);
5296 LOGFOCUS(("GenerateFocusActionId %" PRIu64, id));
5297 return id;
5300 static bool IsInPointerLockContext(nsPIDOMWindowOuter* aWin) {
5301 return PointerLockManager::IsInLockContext(aWin ? aWin->GetBrowsingContext()
5302 : nullptr);
5305 void nsFocusManager::SetFocusedWindowInternal(nsPIDOMWindowOuter* aWindow,
5306 uint64_t aActionId,
5307 bool aSyncBrowsingContext) {
5308 if (XRE_IsParentProcess() && !PointerUnlocker::sActiveUnlocker &&
5309 IsInPointerLockContext(mFocusedWindow) &&
5310 !IsInPointerLockContext(aWindow)) {
5311 nsCOMPtr<nsIRunnable> runnable = new PointerUnlocker();
5312 NS_DispatchToCurrentThread(runnable);
5315 // Update the last focus time on any affected documents
5316 if (aWindow && aWindow != mFocusedWindow) {
5317 const TimeStamp now(TimeStamp::Now());
5318 for (Document* doc = aWindow->GetExtantDoc(); doc;
5319 doc = doc->GetInProcessParentDocument()) {
5320 doc->SetLastFocusTime(now);
5324 // This function may be called with zero action id to indicate that the
5325 // action id should be ignored.
5326 if (XRE_IsContentProcess() && aActionId &&
5327 ActionIdComparableAndLower(aActionId,
5328 mActionIdForFocusedBrowsingContextInContent)) {
5329 // Unclear if this ever happens.
5330 LOGFOCUS(
5331 ("Ignored an attempt to set an in-process BrowsingContext as "
5332 "focused due to stale action id %" PRIu64 ".",
5333 aActionId));
5334 return;
5337 mFocusedWindow = aWindow;
5338 BrowsingContext* bc = aWindow ? aWindow->GetBrowsingContext() : nullptr;
5339 if (aSyncBrowsingContext) {
5340 MOZ_ASSERT(aActionId,
5341 "aActionId must not be zero if aSyncBrowsingContext is true");
5342 SetFocusedBrowsingContext(bc, aActionId);
5343 } else if (XRE_IsContentProcess()) {
5344 MOZ_ASSERT(mFocusedBrowsingContextInContent == bc,
5345 "Not syncing BrowsingContext even when different.");
5349 void nsFocusManager::NotifyOfReFocus(Element& aElement) {
5350 nsPIDOMWindowOuter* window = GetCurrentWindow(&aElement);
5351 if (!window || window != mFocusedWindow) {
5352 return;
5354 if (!aElement.IsInComposedDoc() || IsNonFocusableRoot(&aElement)) {
5355 return;
5357 nsIDocShell* docShell = window->GetDocShell();
5358 if (!docShell) {
5359 return;
5361 RefPtr<PresShell> presShell = docShell->GetPresShell();
5362 if (!presShell) {
5363 return;
5365 RefPtr<nsPresContext> presContext = presShell->GetPresContext();
5366 if (!presContext) {
5367 return;
5369 IMEStateManager::OnReFocus(*presContext, aElement);
5372 void nsFocusManager::MarkUncollectableForCCGeneration(uint32_t aGeneration) {
5373 if (!sInstance) {
5374 return;
5377 if (sInstance->mActiveWindow) {
5378 sInstance->mActiveWindow->MarkUncollectableForCCGeneration(aGeneration);
5380 if (sInstance->mFocusedWindow) {
5381 sInstance->mFocusedWindow->MarkUncollectableForCCGeneration(aGeneration);
5383 if (sInstance->mWindowBeingLowered) {
5384 sInstance->mWindowBeingLowered->MarkUncollectableForCCGeneration(
5385 aGeneration);
5387 if (sInstance->mFocusedElement) {
5388 sInstance->mFocusedElement->OwnerDoc()->MarkUncollectableForCCGeneration(
5389 aGeneration);
5391 if (sInstance->mFirstBlurEvent) {
5392 sInstance->mFirstBlurEvent->OwnerDoc()->MarkUncollectableForCCGeneration(
5393 aGeneration);
5395 if (sInstance->mFirstFocusEvent) {
5396 sInstance->mFirstFocusEvent->OwnerDoc()->MarkUncollectableForCCGeneration(
5397 aGeneration);
5401 bool nsFocusManager::CanSkipFocus(nsIContent* aContent) {
5402 if (!aContent) {
5403 return false;
5406 if (mFocusedElement == aContent) {
5407 return true;
5410 nsIDocShell* ds = aContent->OwnerDoc()->GetDocShell();
5411 if (!ds) {
5412 return true;
5415 if (XRE_IsParentProcess()) {
5416 nsCOMPtr<nsIDocShellTreeItem> root;
5417 ds->GetInProcessRootTreeItem(getter_AddRefs(root));
5418 nsCOMPtr<nsPIDOMWindowOuter> newRootWindow =
5419 root ? root->GetWindow() : nullptr;
5420 if (mActiveWindow != newRootWindow) {
5421 nsPIDOMWindowOuter* outerWindow = aContent->OwnerDoc()->GetWindow();
5422 if (outerWindow && outerWindow->GetFocusedElement() == aContent) {
5423 return true;
5426 } else {
5427 BrowsingContext* bc = aContent->OwnerDoc()->GetBrowsingContext();
5428 BrowsingContext* top = bc ? bc->Top() : nullptr;
5429 if (GetActiveBrowsingContext() != top) {
5430 nsPIDOMWindowOuter* outerWindow = aContent->OwnerDoc()->GetWindow();
5431 if (outerWindow && outerWindow->GetFocusedElement() == aContent) {
5432 return true;
5437 return false;
5440 /* static */
5441 Element* nsFocusManager::GetTheFocusableArea(Element* aTarget,
5442 uint32_t aFlags) {
5443 MOZ_ASSERT(aTarget);
5444 nsIFrame* frame = aTarget->GetPrimaryFrame();
5445 if (!frame) {
5446 return nullptr;
5449 // If focus target is the document element of its Document.
5450 if (aTarget == aTarget->OwnerDoc()->GetRootElement()) {
5451 // the root content can always be focused,
5452 // except in userfocusignored context.
5453 return aTarget;
5456 // If focus target is an area element with one or more shapes that are
5457 // focusable areas.
5458 if (aTarget->IsHTMLElement(nsGkAtoms::area)) {
5459 // HTML areas do not have their own frame, and the img frame we get from
5460 // GetPrimaryFrame() is not relevant as to whether it is focusable or
5461 // not, so we have to do all the relevant checks manually for them.
5462 return frame->IsVisibleConsideringAncestors() &&
5463 aTarget->IsFocusableWithoutStyle()
5464 ? aTarget
5465 : nullptr;
5468 // For these 3 steps mentioned in the spec
5469 // 1. If focus target is an element with one or more scrollable regions that
5470 // are focusable areas
5471 // 2. If focus target is a navigable
5472 // 3. If focus target is a navigable container with a non-null content
5473 // navigable
5474 // nsIFrame::IsFocusable will effectively perform the checks for them.
5475 if (frame->IsFocusable(aFlags & FLAG_BYMOUSE)) {
5476 return aTarget;
5479 // If focus target is a shadow host whose shadow root's delegates focus is
5480 // true
5481 if (ShadowRoot* root = aTarget->GetShadowRoot()) {
5482 if (root->DelegatesFocus()) {
5483 // If focus target is a shadow-including inclusive ancestor of the
5484 // currently focused area of a top-level browsing context's DOM anchor,
5485 // then return the already-focused element.
5486 if (nsPIDOMWindowInner* innerWindow =
5487 aTarget->OwnerDoc()->GetInnerWindow()) {
5488 if (Element* focusedElement = innerWindow->GetFocusedElement()) {
5489 if (focusedElement->IsShadowIncludingInclusiveDescendantOf(aTarget)) {
5490 return focusedElement;
5495 if (Element* firstFocusable =
5496 root->GetFocusDelegate(aFlags & FLAG_BYMOUSE)) {
5497 return firstFocusable;
5501 return nullptr;
5504 nsresult NS_NewFocusManager(nsIFocusManager** aResult) {
5505 NS_IF_ADDREF(*aResult = nsFocusManager::GetFocusManager());
5506 return NS_OK;