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