Bug 1550519 - Show a translucent parent highlight when a subgrid is highlighted....
[gecko.git] / dom / base / nsFocusManager.cpp
blob82b7fcd61e28e2c3c793ab1d56d254c1e616fe50
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 "nsIDOMChromeWindow.h"
19 #include "nsIHTMLDocument.h"
20 #include "nsIDocShell.h"
21 #include "nsIDocShellTreeOwner.h"
22 #include "nsIFormControl.h"
23 #include "nsLayoutUtils.h"
24 #include "nsFrameTraversal.h"
25 #include "nsIWebNavigation.h"
26 #include "nsCaret.h"
27 #include "nsIBaseWindow.h"
28 #include "nsIXULWindow.h"
29 #include "nsViewManager.h"
30 #include "nsFrameSelection.h"
31 #include "mozilla/dom/Document.h"
32 #include "mozilla/dom/Selection.h"
33 #include "nsXULPopupManager.h"
34 #include "nsMenuPopupFrame.h"
35 #include "nsIScriptObjectPrincipal.h"
36 #include "nsIPrincipal.h"
37 #include "nsIObserverService.h"
38 #include "nsIObjectFrame.h"
39 #include "nsBindingManager.h"
40 #include "nsStyleCoord.h"
41 #include "BrowserChild.h"
42 #include "nsFrameLoader.h"
43 #include "nsNumberControlFrame.h"
44 #include "nsNetUtil.h"
45 #include "nsRange.h"
47 #include "mozilla/AccessibleCaretEventHub.h"
48 #include "mozilla/ContentEvents.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/EventDispatcher.h"
57 #include "mozilla/EventStateManager.h"
58 #include "mozilla/EventStates.h"
59 #include "mozilla/HTMLEditor.h"
60 #include "mozilla/IMEStateManager.h"
61 #include "mozilla/LookAndFeel.h"
62 #include "mozilla/Preferences.h"
63 #include "mozilla/PresShell.h"
64 #include "mozilla/Services.h"
65 #include "mozilla/Unused.h"
66 #include <algorithm>
68 #ifdef MOZ_XUL
69 # include "nsIDOMXULMenuListElement.h"
70 #endif
72 #ifdef ACCESSIBILITY
73 # include "nsAccessibilityService.h"
74 #endif
76 #ifndef XP_MACOSX
77 # include "nsIScriptError.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(NS_LITERAL_CSTRING("(none)")); \
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(nsFocusManager, mActiveWindow, mFocusedWindow,
160 mFocusedElement, mFirstBlurEvent, mFirstFocusEvent,
161 mWindowBeingLowered, mDelayedBlurFocusEvents,
162 mMouseButtonEventHandlingDocument)
164 nsFocusManager* nsFocusManager::sInstance = nullptr;
165 bool nsFocusManager::sMouseFocusesFormControl = false;
166 bool nsFocusManager::sTestMode = false;
168 static const char* kObservedPrefs[] = {
169 "accessibility.browsewithcaret", "accessibility.tabfocus_applies_to_xul",
170 "accessibility.mouse_focuses_formcontrol", "focusmanager.testmode",
171 nullptr};
173 nsFocusManager::nsFocusManager() : mEventHandlingNeedsFlush(false) {}
175 nsFocusManager::~nsFocusManager() {
176 Preferences::UnregisterCallbacks(
177 PREF_CHANGE_METHOD(nsFocusManager::PrefChanged), kObservedPrefs, this);
179 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
180 if (obs) {
181 obs->RemoveObserver(this, "xpcom-shutdown");
185 // static
186 nsresult nsFocusManager::Init() {
187 nsFocusManager* fm = new nsFocusManager();
188 NS_ADDREF(fm);
189 sInstance = fm;
191 nsIContent::sTabFocusModelAppliesToXUL =
192 Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
193 nsIContent::sTabFocusModelAppliesToXUL);
195 sMouseFocusesFormControl =
196 Preferences::GetBool("accessibility.mouse_focuses_formcontrol", false);
198 sTestMode = Preferences::GetBool("focusmanager.testmode", false);
200 Preferences::RegisterCallbacks(
201 PREF_CHANGE_METHOD(nsFocusManager::PrefChanged), kObservedPrefs, fm);
203 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
204 if (obs) {
205 obs->AddObserver(fm, "xpcom-shutdown", true);
208 return NS_OK;
211 // static
212 void nsFocusManager::Shutdown() { NS_IF_RELEASE(sInstance); }
214 void nsFocusManager::PrefChanged(const char* aPref) {
215 nsDependentCString pref(aPref);
216 if (pref.EqualsLiteral("accessibility.browsewithcaret")) {
217 UpdateCaretForCaretBrowsingMode();
218 } else if (pref.EqualsLiteral("accessibility.tabfocus_applies_to_xul")) {
219 nsIContent::sTabFocusModelAppliesToXUL =
220 Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
221 nsIContent::sTabFocusModelAppliesToXUL);
222 } else if (pref.EqualsLiteral("accessibility.mouse_focuses_formcontrol")) {
223 sMouseFocusesFormControl =
224 Preferences::GetBool("accessibility.mouse_focuses_formcontrol", false);
225 } else if (pref.EqualsLiteral("focusmanager.testmode")) {
226 sTestMode = Preferences::GetBool("focusmanager.testmode", false);
230 NS_IMETHODIMP
231 nsFocusManager::Observe(nsISupports* aSubject, const char* aTopic,
232 const char16_t* aData) {
233 if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
234 mActiveWindow = nullptr;
235 mFocusedWindow = nullptr;
236 mFocusedElement = nullptr;
237 mFirstBlurEvent = nullptr;
238 mFirstFocusEvent = nullptr;
239 mWindowBeingLowered = nullptr;
240 mDelayedBlurFocusEvents.Clear();
241 mMouseButtonEventHandlingDocument = nullptr;
244 return NS_OK;
247 // given a frame content node, retrieve the nsIDOMWindow displayed in it
248 static nsPIDOMWindowOuter* GetContentWindow(nsIContent* aContent) {
249 Document* doc = aContent->GetComposedDoc();
250 if (doc) {
251 Document* subdoc = doc->GetSubDocumentFor(aContent);
252 if (subdoc) return subdoc->GetWindow();
255 return nullptr;
258 bool nsFocusManager::IsFocused(nsIContent* aContent) {
259 if (!aContent || !mFocusedElement) {
260 return false;
262 return aContent == mFocusedElement;
265 bool nsFocusManager::IsTestMode() { return sTestMode; }
267 // get the current window for the given content node
268 static nsPIDOMWindowOuter* GetCurrentWindow(nsIContent* aContent) {
269 Document* doc = aContent->GetComposedDoc();
270 return doc ? doc->GetWindow() : nullptr;
273 // static
274 Element* nsFocusManager::GetFocusedDescendant(
275 nsPIDOMWindowOuter* aWindow, SearchRange aSearchRange,
276 nsPIDOMWindowOuter** aFocusedWindow) {
277 NS_ENSURE_TRUE(aWindow, nullptr);
279 *aFocusedWindow = nullptr;
281 Element* currentElement = nullptr;
282 nsPIDOMWindowOuter* window = aWindow;
283 for (;;) {
284 *aFocusedWindow = window;
285 currentElement = window->GetFocusedElement();
286 if (!currentElement || aSearchRange == eOnlyCurrentWindow) {
287 break;
290 window = GetContentWindow(currentElement);
291 if (!window) {
292 break;
295 if (aSearchRange == eIncludeAllDescendants) {
296 continue;
299 MOZ_ASSERT(aSearchRange == eIncludeVisibleDescendants);
301 // If the child window doesn't have PresShell, it means the window is
302 // invisible.
303 nsIDocShell* docShell = window->GetDocShell();
304 if (!docShell) {
305 break;
307 if (!docShell->GetPresShell()) {
308 break;
312 NS_IF_ADDREF(*aFocusedWindow);
314 return currentElement;
317 // static
318 Element* nsFocusManager::GetRedirectedFocus(nsIContent* aContent) {
319 // For input number, redirect focus to our anonymous text control.
320 if (aContent->IsHTMLElement(nsGkAtoms::input)) {
321 bool typeIsNumber =
322 static_cast<dom::HTMLInputElement*>(aContent)->ControlType() ==
323 NS_FORM_INPUT_NUMBER;
325 if (typeIsNumber) {
326 nsNumberControlFrame* numberControlFrame =
327 do_QueryFrame(aContent->GetPrimaryFrame());
329 if (numberControlFrame) {
330 HTMLInputElement* textControl =
331 numberControlFrame->GetAnonTextControl();
332 return textControl;
337 #ifdef MOZ_XUL
338 if (aContent->IsXULElement()) {
339 if (aContent->IsXULElement(nsGkAtoms::textbox)) {
340 return aContent->OwnerDoc()->GetAnonymousElementByAttribute(
341 aContent, nsGkAtoms::anonid, NS_LITERAL_STRING("input"));
342 } else {
343 nsCOMPtr<nsIDOMXULMenuListElement> menulist =
344 aContent->AsElement()->AsXULMenuList();
345 if (menulist) {
346 RefPtr<Element> inputField;
347 menulist->GetInputField(getter_AddRefs(inputField));
348 return inputField;
352 #endif
354 return nullptr;
357 // static
358 InputContextAction::Cause nsFocusManager::GetFocusMoveActionCause(
359 uint32_t aFlags) {
360 if (aFlags & nsIFocusManager::FLAG_BYTOUCH) {
361 return InputContextAction::CAUSE_TOUCH;
362 } else if (aFlags & nsIFocusManager::FLAG_BYMOUSE) {
363 return InputContextAction::CAUSE_MOUSE;
364 } else if (aFlags & nsIFocusManager::FLAG_BYKEY) {
365 return InputContextAction::CAUSE_KEY;
367 return InputContextAction::CAUSE_UNKNOWN;
370 NS_IMETHODIMP
371 nsFocusManager::GetActiveWindow(mozIDOMWindowProxy** aWindow) {
372 NS_IF_ADDREF(*aWindow = mActiveWindow);
373 return NS_OK;
376 void nsFocusManager::FocusWindow(nsPIDOMWindowOuter* aWindow) {
377 if (RefPtr<nsFocusManager> fm = sInstance) {
378 fm->SetFocusedWindow(aWindow);
382 NS_IMETHODIMP
383 nsFocusManager::SetActiveWindow(mozIDOMWindowProxy* aWindow) {
384 NS_ENSURE_STATE(aWindow);
386 // only top-level windows can be made active
387 nsCOMPtr<nsPIDOMWindowOuter> piWindow = nsPIDOMWindowOuter::From(aWindow);
388 NS_ENSURE_TRUE(piWindow == piWindow->GetPrivateRoot(), NS_ERROR_INVALID_ARG);
390 RaiseWindow(piWindow);
391 return NS_OK;
394 NS_IMETHODIMP
395 nsFocusManager::GetFocusedWindow(mozIDOMWindowProxy** aFocusedWindow) {
396 NS_IF_ADDREF(*aFocusedWindow = mFocusedWindow);
397 return NS_OK;
400 NS_IMETHODIMP nsFocusManager::SetFocusedWindow(
401 mozIDOMWindowProxy* aWindowToFocus) {
402 LOGFOCUS(("<<SetFocusedWindow begin>>"));
404 nsCOMPtr<nsPIDOMWindowOuter> windowToFocus =
405 nsPIDOMWindowOuter::From(aWindowToFocus);
406 NS_ENSURE_TRUE(windowToFocus, NS_ERROR_FAILURE);
408 nsCOMPtr<Element> frameElement = windowToFocus->GetFrameElementInternal();
409 if (frameElement) {
410 // pass false for aFocusChanged so that the caret does not get updated
411 // and scrolling does not occur.
412 SetFocusInner(frameElement, 0, false, true);
413 } else {
414 // this is a top-level window. If the window has a child frame focused,
415 // clear the focus. Otherwise, focus should already be in this frame, or
416 // already cleared. This ensures that focus will be in this frame and not
417 // in a child.
418 nsIContent* content = windowToFocus->GetFocusedElement();
419 if (content) {
420 if (nsCOMPtr<nsPIDOMWindowOuter> childWindow = GetContentWindow(content))
421 ClearFocus(windowToFocus);
425 nsCOMPtr<nsPIDOMWindowOuter> rootWindow = windowToFocus->GetPrivateRoot();
426 if (rootWindow) RaiseWindow(rootWindow);
428 LOGFOCUS(("<<SetFocusedWindow end>>"));
430 return NS_OK;
433 NS_IMETHODIMP
434 nsFocusManager::GetFocusedElement(Element** aFocusedElement) {
435 RefPtr<Element> focusedElement = mFocusedElement;
436 focusedElement.forget(aFocusedElement);
437 return NS_OK;
440 NS_IMETHODIMP
441 nsFocusManager::GetLastFocusMethod(mozIDOMWindowProxy* aWindow,
442 uint32_t* aLastFocusMethod) {
443 // the focus method is stored on the inner window
444 nsCOMPtr<nsPIDOMWindowOuter> window;
445 if (aWindow) {
446 window = nsPIDOMWindowOuter::From(aWindow);
448 if (!window) window = mFocusedWindow;
450 *aLastFocusMethod = window ? window->GetFocusMethod() : 0;
452 NS_ASSERTION((*aLastFocusMethod & FOCUSMETHOD_MASK) == *aLastFocusMethod,
453 "invalid focus method");
454 return NS_OK;
457 NS_IMETHODIMP
458 nsFocusManager::SetFocus(Element* aElement, uint32_t aFlags) {
459 LOGFOCUS(("<<SetFocus begin>>"));
461 NS_ENSURE_ARG(aElement);
463 SetFocusInner(aElement, aFlags, true, true);
465 LOGFOCUS(("<<SetFocus end>>"));
467 return NS_OK;
470 NS_IMETHODIMP
471 nsFocusManager::ElementIsFocusable(Element* aElement, uint32_t aFlags,
472 bool* aIsFocusable) {
473 NS_ENSURE_TRUE(aElement, NS_ERROR_INVALID_ARG);
475 *aIsFocusable = FlushAndCheckIfFocusable(aElement, aFlags) != nullptr;
477 return NS_OK;
480 NS_IMETHODIMP
481 nsFocusManager::MoveFocus(mozIDOMWindowProxy* aWindow, Element* aStartElement,
482 uint32_t aType, uint32_t aFlags, Element** aElement) {
483 *aElement = nullptr;
485 LOGFOCUS(("<<MoveFocus begin Type: %d Flags: %x>>", aType, aFlags));
487 if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug) && mFocusedWindow) {
488 Document* doc = mFocusedWindow->GetExtantDoc();
489 if (doc && doc->GetDocumentURI()) {
490 LOGFOCUS((" Focused Window: %p %s", mFocusedWindow.get(),
491 doc->GetDocumentURI()->GetSpecOrDefault().get()));
495 LOGCONTENT(" Current Focus: %s", mFocusedElement.get());
497 // use FLAG_BYMOVEFOCUS when switching focus with MoveFocus unless one of
498 // the other focus methods is already set, or we're just moving to the root
499 // or caret position.
500 if (aType != MOVEFOCUS_ROOT && aType != MOVEFOCUS_CARET &&
501 (aFlags & FOCUSMETHOD_MASK) == 0) {
502 aFlags |= FLAG_BYMOVEFOCUS;
505 nsCOMPtr<nsPIDOMWindowOuter> window;
506 if (aStartElement) {
507 window = GetCurrentWindow(aStartElement);
508 } else {
509 window = aWindow ? nsPIDOMWindowOuter::From(aWindow) : mFocusedWindow.get();
512 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
514 bool noParentTraversal = aFlags & FLAG_NOPARENTFRAME;
515 nsCOMPtr<nsIContent> newFocus;
516 nsresult rv =
517 DetermineElementToMoveFocus(window, aStartElement, aType,
518 noParentTraversal, getter_AddRefs(newFocus));
519 if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
520 return NS_OK;
523 NS_ENSURE_SUCCESS(rv, rv);
525 LOGCONTENTNAVIGATION("Element to be focused: %s", newFocus.get());
527 if (newFocus && newFocus->IsElement()) {
528 // for caret movement, pass false for the aFocusChanged argument,
529 // otherwise the caret will end up moving to the focus position. This
530 // would be a problem because the caret would move to the beginning of the
531 // focused link making it impossible to navigate the caret over a link.
532 SetFocusInner(MOZ_KnownLive(newFocus->AsElement()), aFlags,
533 aType != MOVEFOCUS_CARET, true);
534 *aElement = do_AddRef(newFocus->AsElement()).take();
535 } else if (aType == MOVEFOCUS_ROOT || aType == MOVEFOCUS_CARET) {
536 // no content was found, so clear the focus for these two types.
537 ClearFocus(window);
540 LOGFOCUS(("<<MoveFocus end>>"));
542 return NS_OK;
545 NS_IMETHODIMP
546 nsFocusManager::ClearFocus(mozIDOMWindowProxy* aWindow) {
547 LOGFOCUS(("<<ClearFocus begin>>"));
549 // if the window to clear is the focused window or an ancestor of the
550 // focused window, then blur the existing focused content. Otherwise, the
551 // focus is somewhere else so just update the current node.
552 NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
553 nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
555 if (IsSameOrAncestor(window, mFocusedWindow)) {
556 bool isAncestor = (window != mFocusedWindow);
557 if (Blur(window, nullptr, isAncestor, true)) {
558 // if we are clearing the focus on an ancestor of the focused window,
559 // the ancestor will become the new focused window, so focus it
560 if (isAncestor) Focus(window, nullptr, 0, true, false, false, true);
562 } else {
563 window->SetFocusedElement(nullptr);
566 LOGFOCUS(("<<ClearFocus end>>"));
568 return NS_OK;
571 NS_IMETHODIMP
572 nsFocusManager::GetFocusedElementForWindow(mozIDOMWindowProxy* aWindow,
573 bool aDeep,
574 mozIDOMWindowProxy** aFocusedWindow,
575 Element** aElement) {
576 *aElement = nullptr;
577 if (aFocusedWindow) *aFocusedWindow = nullptr;
579 NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
580 nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
582 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
583 RefPtr<Element> focusedElement =
584 GetFocusedDescendant(window,
585 aDeep ? nsFocusManager::eIncludeAllDescendants
586 : nsFocusManager::eOnlyCurrentWindow,
587 getter_AddRefs(focusedWindow));
589 focusedElement.forget(aElement);
591 if (aFocusedWindow) NS_IF_ADDREF(*aFocusedWindow = focusedWindow);
593 return NS_OK;
596 NS_IMETHODIMP
597 nsFocusManager::MoveCaretToFocus(mozIDOMWindowProxy* aWindow) {
598 nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(aWindow);
599 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav);
600 if (dsti) {
601 if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) {
602 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(dsti);
603 NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
605 // don't move the caret for editable documents
606 bool isEditable;
607 docShell->GetEditable(&isEditable);
608 if (isEditable) return NS_OK;
610 RefPtr<PresShell> presShell = docShell->GetPresShell();
611 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
613 nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
614 nsCOMPtr<nsIContent> content = window->GetFocusedElement();
615 if (content) {
616 MoveCaretToFocus(presShell, content);
621 return NS_OK;
624 NS_IMETHODIMP
625 nsFocusManager::WindowRaised(mozIDOMWindowProxy* aWindow) {
626 NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
627 nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
629 if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
630 LOGFOCUS(("Window %p Raised [Currently: %p %p]", aWindow,
631 mActiveWindow.get(), mFocusedWindow.get()));
632 Document* doc = window->GetExtantDoc();
633 if (doc && doc->GetDocumentURI()) {
634 LOGFOCUS((" Raised Window: %p %s", aWindow,
635 doc->GetDocumentURI()->GetSpecOrDefault().get()));
637 if (mActiveWindow) {
638 doc = mActiveWindow->GetExtantDoc();
639 if (doc && doc->GetDocumentURI()) {
640 LOGFOCUS((" Active Window: %p %s", mActiveWindow.get(),
641 doc->GetDocumentURI()->GetSpecOrDefault().get()));
646 if (mActiveWindow == window) {
647 // The window is already active, so there is no need to focus anything,
648 // but make sure that the right widget is focused. This is a special case
649 // for Windows because when restoring a minimized window, a second
650 // activation will occur and the top-level widget could be focused instead
651 // of the child we want. We solve this by calling SetFocus to ensure that
652 // what the focus manager thinks should be the current widget is actually
653 // focused.
654 EnsureCurrentWidgetFocused();
655 return NS_OK;
658 // lower the existing window, if any. This shouldn't happen usually.
659 if (mActiveWindow) WindowLowered(mActiveWindow);
661 nsCOMPtr<nsIDocShellTreeItem> docShellAsItem = window->GetDocShell();
662 // If there's no docShellAsItem, this window must have been closed,
663 // in that case there is no tree owner.
664 NS_ENSURE_TRUE(docShellAsItem, NS_OK);
666 // set this as the active window
667 mActiveWindow = window;
669 // ensure that the window is enabled and visible
670 nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
671 docShellAsItem->GetTreeOwner(getter_AddRefs(treeOwner));
672 nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner);
673 if (baseWindow) {
674 bool isEnabled = true;
675 if (NS_SUCCEEDED(baseWindow->GetEnabled(&isEnabled)) && !isEnabled) {
676 return NS_ERROR_FAILURE;
679 baseWindow->SetVisibility(true);
682 // If this is a parent or single process window, send the activate event.
683 // Events for child process windows will be sent when ParentActivated
684 // is called.
685 if (XRE_IsParentProcess()) {
686 ActivateOrDeactivate(window, true);
689 // retrieve the last focused element within the window that was raised
690 nsCOMPtr<nsPIDOMWindowOuter> currentWindow;
691 RefPtr<Element> currentFocus = GetFocusedDescendant(
692 window, eIncludeAllDescendants, getter_AddRefs(currentWindow));
694 NS_ASSERTION(currentWindow, "window raised with no window current");
695 if (!currentWindow) return NS_OK;
697 // If there is no nsIXULWindow, then this is an embedded or child process
698 // window. Pass false for aWindowRaised so that commands get updated.
699 nsCOMPtr<nsIXULWindow> xulWin(do_GetInterface(baseWindow));
700 Focus(currentWindow, currentFocus, 0, true, false, xulWin != nullptr, true);
702 return NS_OK;
705 NS_IMETHODIMP
706 nsFocusManager::WindowLowered(mozIDOMWindowProxy* aWindow) {
707 NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
708 nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
710 if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
711 LOGFOCUS(("Window %p Lowered [Currently: %p %p]", aWindow,
712 mActiveWindow.get(), mFocusedWindow.get()));
713 Document* doc = window->GetExtantDoc();
714 if (doc && doc->GetDocumentURI()) {
715 LOGFOCUS((" Lowered Window: %s",
716 doc->GetDocumentURI()->GetSpecOrDefault().get()));
718 if (mActiveWindow) {
719 doc = mActiveWindow->GetExtantDoc();
720 if (doc && doc->GetDocumentURI()) {
721 LOGFOCUS((" Active Window: %s",
722 doc->GetDocumentURI()->GetSpecOrDefault().get()));
727 if (mActiveWindow != window) return NS_OK;
729 // clear the mouse capture as the active window has changed
730 PresShell::ReleaseCapturingContent();
732 // In addition, reset the drag state to ensure that we are no longer in
733 // drag-select mode.
734 if (mFocusedWindow) {
735 nsCOMPtr<nsIDocShell> docShell = mFocusedWindow->GetDocShell();
736 if (docShell) {
737 if (PresShell* presShell = docShell->GetPresShell()) {
738 RefPtr<nsFrameSelection> frameSelection = presShell->FrameSelection();
739 frameSelection->SetDragState(false);
744 // If this is a parent or single process window, send the deactivate event.
745 // Events for child process windows will be sent when ParentActivated
746 // is called.
747 if (XRE_IsParentProcess()) {
748 ActivateOrDeactivate(window, false);
751 // keep track of the window being lowered, so that attempts to raise the
752 // window can be prevented until we return. Otherwise, focus can get into
753 // an unusual state.
754 mWindowBeingLowered = mActiveWindow;
755 mActiveWindow = nullptr;
757 if (mFocusedWindow) Blur(nullptr, nullptr, true, true);
759 mWindowBeingLowered = nullptr;
761 return NS_OK;
764 nsresult nsFocusManager::ContentRemoved(Document* aDocument,
765 nsIContent* aContent) {
766 NS_ENSURE_ARG(aDocument);
767 NS_ENSURE_ARG(aContent);
769 nsPIDOMWindowOuter* window = aDocument->GetWindow();
770 if (!window) return NS_OK;
772 // if the content is currently focused in the window, or is an
773 // shadow-including inclusive ancestor of the currently focused element,
774 // reset the focus within that window.
775 nsIContent* content = window->GetFocusedElement();
776 if (content &&
777 nsContentUtils::ContentIsHostIncludingDescendantOf(content, aContent)) {
778 bool shouldShowFocusRing = window->ShouldShowFocusRing();
779 window->SetFocusedElement(nullptr);
781 // if this window is currently focused, clear the global focused
782 // element as well, but don't fire any events.
783 if (window == mFocusedWindow) {
784 mFocusedElement = nullptr;
785 } else {
786 // Check if the node that was focused is an iframe or similar by looking
787 // if it has a subdocument. This would indicate that this focused iframe
788 // and its descendants will be going away. We will need to move the
789 // focus somewhere else, so just clear the focus in the toplevel window
790 // so that no element is focused.
791 Document* subdoc = aDocument->GetSubDocumentFor(content);
792 if (subdoc) {
793 nsCOMPtr<nsIDocShell> docShell = subdoc->GetDocShell();
794 if (docShell) {
795 nsCOMPtr<nsPIDOMWindowOuter> childWindow = docShell->GetWindow();
796 if (childWindow && IsSameOrAncestor(childWindow, mFocusedWindow)) {
797 ClearFocus(mActiveWindow);
803 // Notify the editor in case we removed its ancestor limiter.
804 if (content->IsEditable()) {
805 nsCOMPtr<nsIDocShell> docShell = aDocument->GetDocShell();
806 if (docShell) {
807 RefPtr<HTMLEditor> htmlEditor = docShell->GetHTMLEditor();
808 if (htmlEditor) {
809 RefPtr<Selection> selection = htmlEditor->GetSelection();
810 if (selection && selection->GetFrameSelection() &&
811 content == selection->GetFrameSelection()->GetAncestorLimiter()) {
812 htmlEditor->FinalizeSelection();
818 NotifyFocusStateChange(content, nullptr, shouldShowFocusRing, false);
821 return NS_OK;
824 NS_IMETHODIMP
825 nsFocusManager::WindowShown(mozIDOMWindowProxy* aWindow, bool aNeedsFocus) {
826 NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
827 nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
829 if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
830 LOGFOCUS(("Window %p Shown [Currently: %p %p]", window.get(),
831 mActiveWindow.get(), mFocusedWindow.get()));
832 Document* doc = window->GetExtantDoc();
833 if (doc && doc->GetDocumentURI()) {
834 LOGFOCUS(("Shown Window: %s",
835 doc->GetDocumentURI()->GetSpecOrDefault().get()));
838 if (mFocusedWindow) {
839 doc = mFocusedWindow->GetExtantDoc();
840 if (doc && doc->GetDocumentURI()) {
841 LOGFOCUS((" Focused Window: %s",
842 doc->GetDocumentURI()->GetSpecOrDefault().get()));
847 if (nsIDocShell* docShell = window->GetDocShell()) {
848 if (nsCOMPtr<nsIBrowserChild> child = docShell->GetBrowserChild()) {
849 bool active = static_cast<BrowserChild*>(child.get())->ParentIsActive();
850 ActivateOrDeactivate(window, active);
854 if (mFocusedWindow != window) return NS_OK;
856 if (aNeedsFocus) {
857 nsCOMPtr<nsPIDOMWindowOuter> currentWindow;
858 RefPtr<Element> currentFocus = GetFocusedDescendant(
859 window, eIncludeAllDescendants, getter_AddRefs(currentWindow));
860 if (currentWindow)
861 Focus(currentWindow, currentFocus, 0, true, false, false, true);
862 } else {
863 // Sometimes, an element in a window can be focused before the window is
864 // visible, which would mean that the widget may not be properly focused.
865 // When the window becomes visible, make sure the right widget is focused.
866 EnsureCurrentWidgetFocused();
869 return NS_OK;
872 NS_IMETHODIMP
873 nsFocusManager::WindowHidden(mozIDOMWindowProxy* aWindow) {
874 // if there is no window or it is not the same or an ancestor of the
875 // currently focused window, just return, as the current focus will not
876 // be affected.
878 NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
879 nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
881 if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
882 LOGFOCUS(("Window %p Hidden [Currently: %p %p]", window.get(),
883 mActiveWindow.get(), mFocusedWindow.get()));
884 nsAutoCString spec;
885 Document* doc = window->GetExtantDoc();
886 if (doc && doc->GetDocumentURI()) {
887 LOGFOCUS((" Hide Window: %s",
888 doc->GetDocumentURI()->GetSpecOrDefault().get()));
891 if (mFocusedWindow) {
892 doc = mFocusedWindow->GetExtantDoc();
893 if (doc && doc->GetDocumentURI()) {
894 LOGFOCUS((" Focused Window: %s",
895 doc->GetDocumentURI()->GetSpecOrDefault().get()));
899 if (mActiveWindow) {
900 doc = mActiveWindow->GetExtantDoc();
901 if (doc && doc->GetDocumentURI()) {
902 LOGFOCUS((" Active Window: %s",
903 doc->GetDocumentURI()->GetSpecOrDefault().get()));
908 if (!IsSameOrAncestor(window, mFocusedWindow)) return NS_OK;
910 // at this point, we know that the window being hidden is either the focused
911 // window, or an ancestor of the focused window. Either way, the focus is no
912 // longer valid, so it needs to be updated.
914 RefPtr<Element> oldFocusedElement = mFocusedElement.forget();
916 nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
917 RefPtr<PresShell> presShell = focusedDocShell->GetPresShell();
919 if (oldFocusedElement && oldFocusedElement->IsInComposedDoc()) {
920 NotifyFocusStateChange(oldFocusedElement, nullptr,
921 mFocusedWindow->ShouldShowFocusRing(), false);
922 window->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
924 if (presShell) {
925 SendFocusOrBlurEvent(eBlur, presShell,
926 oldFocusedElement->GetComposedDoc(),
927 oldFocusedElement, 1, false);
931 nsPresContext* focusedPresContext =
932 presShell ? presShell->GetPresContext() : nullptr;
933 IMEStateManager::OnChangeFocus(focusedPresContext, nullptr,
934 GetFocusMoveActionCause(0));
935 if (presShell) {
936 SetCaretVisible(presShell, false, nullptr);
939 // if the docshell being hidden is being destroyed, then we want to move
940 // focus somewhere else. Call ClearFocus on the toplevel window, which
941 // will have the effect of clearing the focus and moving the focused window
942 // to the toplevel window. But if the window isn't being destroyed, we are
943 // likely just loading a new document in it, so we want to maintain the
944 // focused window so that the new document gets properly focused.
945 nsCOMPtr<nsIDocShell> docShellBeingHidden = window->GetDocShell();
946 bool beingDestroyed = !docShellBeingHidden;
947 if (docShellBeingHidden) {
948 docShellBeingHidden->IsBeingDestroyed(&beingDestroyed);
950 if (beingDestroyed) {
951 // There is usually no need to do anything if a toplevel window is going
952 // away, as we assume that WindowLowered will be called. However, this may
953 // not happen if nsIAppStartup::eForceQuit is used to quit, and can cause
954 // a leak. So if the active window is being destroyed, call WindowLowered
955 // directly.
956 if (mActiveWindow == mFocusedWindow || mActiveWindow == window)
957 WindowLowered(mActiveWindow);
958 else
959 ClearFocus(mActiveWindow);
960 return NS_OK;
963 // if the window being hidden is an ancestor of the focused window, adjust
964 // the focused window so that it points to the one being hidden. This
965 // ensures that the focused window isn't in a chain of frames that doesn't
966 // exist any more.
967 if (window != mFocusedWindow) {
968 nsCOMPtr<nsIDocShellTreeItem> dsti =
969 mFocusedWindow ? mFocusedWindow->GetDocShell() : nullptr;
970 if (dsti) {
971 nsCOMPtr<nsIDocShellTreeItem> parentDsti;
972 dsti->GetParent(getter_AddRefs(parentDsti));
973 if (parentDsti) {
974 if (nsCOMPtr<nsPIDOMWindowOuter> parentWindow = parentDsti->GetWindow())
975 parentWindow->SetFocusedElement(nullptr);
979 SetFocusedWindowInternal(window);
982 return NS_OK;
985 NS_IMETHODIMP
986 nsFocusManager::FireDelayedEvents(Document* aDocument) {
987 NS_ENSURE_ARG(aDocument);
989 // fire any delayed focus and blur events in the same order that they were
990 // added
991 for (uint32_t i = 0; i < mDelayedBlurFocusEvents.Length(); i++) {
992 if (mDelayedBlurFocusEvents[i].mDocument == aDocument) {
993 if (!aDocument->GetInnerWindow() ||
994 !aDocument->GetInnerWindow()->IsCurrentInnerWindow()) {
995 // If the document was navigated away from or is defunct, don't bother
996 // firing events on it. Note the symmetry between this condition and
997 // the similar one in Document.cpp:FireOrClearDelayedEvents.
998 mDelayedBlurFocusEvents.RemoveElementAt(i);
999 --i;
1000 } else if (!aDocument->EventHandlingSuppressed()) {
1001 EventMessage message = mDelayedBlurFocusEvents[i].mEventMessage;
1002 nsCOMPtr<EventTarget> target = mDelayedBlurFocusEvents[i].mTarget;
1003 RefPtr<PresShell> presShell = mDelayedBlurFocusEvents[i].mPresShell;
1004 nsCOMPtr<EventTarget> relatedTarget =
1005 mDelayedBlurFocusEvents[i].mRelatedTarget;
1006 mDelayedBlurFocusEvents.RemoveElementAt(i);
1008 FireFocusOrBlurEvent(message, presShell, target, false, false,
1009 relatedTarget);
1010 --i;
1015 return NS_OK;
1018 NS_IMETHODIMP
1019 nsFocusManager::FocusPlugin(Element* aPlugin) {
1020 NS_ENSURE_ARG(aPlugin);
1021 SetFocusInner(aPlugin, 0, true, false);
1022 return NS_OK;
1025 NS_IMETHODIMP
1026 nsFocusManager::ParentActivated(mozIDOMWindowProxy* aWindow, bool aActive) {
1027 nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
1028 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
1030 ActivateOrDeactivate(window, aActive);
1031 return NS_OK;
1034 /* static */
1035 void nsFocusManager::NotifyFocusStateChange(nsIContent* aContent,
1036 nsIContent* aContentToFocus,
1037 bool aWindowShouldShowFocusRing,
1038 bool aGettingFocus) {
1039 MOZ_ASSERT_IF(aContentToFocus, !aGettingFocus);
1040 if (!aContent->IsElement()) {
1041 return;
1044 nsIContent* commonAncestor = nullptr;
1045 if (aContentToFocus && aContentToFocus->IsElement()) {
1046 commonAncestor = nsContentUtils::GetCommonFlattenedTreeAncestor(
1047 aContent, aContentToFocus);
1050 EventStates eventState = NS_EVENT_STATE_FOCUS;
1051 if (aWindowShouldShowFocusRing) {
1052 eventState |= NS_EVENT_STATE_FOCUSRING;
1055 if (aGettingFocus) {
1056 aContent->AsElement()->AddStates(eventState);
1057 } else {
1058 aContent->AsElement()->RemoveStates(eventState);
1061 for (nsIContent* content = aContent; content && content != commonAncestor;
1062 content = content->GetFlattenedTreeParent()) {
1063 if (!content->IsElement()) {
1064 continue;
1067 Element* element = content->AsElement();
1068 if (aGettingFocus) {
1069 if (element->State().HasState(NS_EVENT_STATE_FOCUS_WITHIN)) {
1070 break;
1072 element->AddStates(NS_EVENT_STATE_FOCUS_WITHIN);
1073 } else {
1074 element->RemoveStates(NS_EVENT_STATE_FOCUS_WITHIN);
1079 // static
1080 void nsFocusManager::EnsureCurrentWidgetFocused() {
1081 if (!mFocusedWindow || sTestMode) return;
1083 // get the main child widget for the focused window and ensure that the
1084 // platform knows that this widget is focused.
1085 nsCOMPtr<nsIDocShell> docShell = mFocusedWindow->GetDocShell();
1086 if (!docShell) {
1087 return;
1089 RefPtr<PresShell> presShell = docShell->GetPresShell();
1090 if (!presShell) {
1091 return;
1093 nsViewManager* vm = presShell->GetViewManager();
1094 if (!vm) {
1095 return;
1097 nsCOMPtr<nsIWidget> widget;
1098 vm->GetRootWidget(getter_AddRefs(widget));
1099 if (!widget) {
1100 return;
1102 widget->SetFocus(nsIWidget::Raise::No);
1105 bool ActivateOrDeactivateChild(BrowserParent* aParent, void* aArg) {
1106 bool active = static_cast<bool>(aArg);
1107 Unused << aParent->SendParentActivated(active);
1108 return false;
1111 void nsFocusManager::ActivateOrDeactivate(nsPIDOMWindowOuter* aWindow,
1112 bool aActive) {
1113 if (!aWindow) {
1114 return;
1117 // Inform the DOM window that it has activated or deactivated, so that
1118 // the active attribute is updated on the window.
1119 aWindow->ActivateOrDeactivate(aActive);
1121 // Send the activate event.
1122 if (aWindow->GetExtantDoc()) {
1123 nsContentUtils::DispatchEventOnlyToChrome(
1124 aWindow->GetExtantDoc(), aWindow->GetCurrentInnerWindow(),
1125 aActive ? NS_LITERAL_STRING("activate")
1126 : NS_LITERAL_STRING("deactivate"),
1127 CanBubble::eYes, Cancelable::eYes, nullptr);
1130 // Look for any remote child frames, iterate over them and send the activation
1131 // notification.
1132 nsContentUtils::CallOnAllRemoteChildren(aWindow, ActivateOrDeactivateChild,
1133 (void*)aActive);
1136 void nsFocusManager::SetFocusInner(Element* aNewContent, int32_t aFlags,
1137 bool aFocusChanged, bool aAdjustWidget) {
1138 // if the element is not focusable, just return and leave the focus as is
1139 RefPtr<Element> elementToFocus =
1140 FlushAndCheckIfFocusable(aNewContent, aFlags);
1141 if (!elementToFocus) {
1142 return;
1145 // check if the element to focus is a frame (iframe) containing a child
1146 // document. Frames are never directly focused; instead focusing a frame
1147 // means focus what is inside the frame. To do this, the descendant content
1148 // within the frame is retrieved and that will be focused instead.
1149 nsCOMPtr<nsPIDOMWindowOuter> newWindow;
1150 nsCOMPtr<nsPIDOMWindowOuter> subWindow = GetContentWindow(elementToFocus);
1151 if (subWindow) {
1152 elementToFocus = GetFocusedDescendant(subWindow, eIncludeAllDescendants,
1153 getter_AddRefs(newWindow));
1154 // since a window is being refocused, clear aFocusChanged so that the
1155 // caret position isn't updated.
1156 aFocusChanged = false;
1159 // unless it was set above, retrieve the window for the element to focus
1160 if (!newWindow) {
1161 newWindow = GetCurrentWindow(elementToFocus);
1164 // if the element is already focused, just return. Note that this happens
1165 // after the frame check above so that we compare the element that will be
1166 // focused rather than the frame it is in.
1167 if (!newWindow ||
1168 (newWindow == mFocusedWindow && elementToFocus == mFocusedElement)) {
1169 return;
1172 // don't allow focus to be placed in docshells or descendants of docshells
1173 // that are being destroyed. Also, ensure that the page hasn't been
1174 // unloaded. The prevents content from being refocused during an unload event.
1175 nsCOMPtr<nsIDocShell> newDocShell = newWindow->GetDocShell();
1176 nsCOMPtr<nsIDocShell> docShell = newDocShell;
1177 while (docShell) {
1178 bool inUnload;
1179 docShell->GetIsInUnload(&inUnload);
1180 if (inUnload) return;
1182 bool beingDestroyed;
1183 docShell->IsBeingDestroyed(&beingDestroyed);
1184 if (beingDestroyed) return;
1186 nsCOMPtr<nsIDocShellTreeItem> parentDsti;
1187 docShell->GetParent(getter_AddRefs(parentDsti));
1188 docShell = do_QueryInterface(parentDsti);
1191 // if the new element is in the same window as the currently focused element
1192 bool isElementInFocusedWindow = (mFocusedWindow == newWindow);
1194 if (!isElementInFocusedWindow && mFocusedWindow && newWindow &&
1195 nsContentUtils::IsHandlingKeyBoardEvent()) {
1196 nsCOMPtr<nsIScriptObjectPrincipal> focused =
1197 do_QueryInterface(mFocusedWindow);
1198 nsCOMPtr<nsIScriptObjectPrincipal> newFocus = do_QueryInterface(newWindow);
1199 nsIPrincipal* focusedPrincipal = focused->GetPrincipal();
1200 nsIPrincipal* newPrincipal = newFocus->GetPrincipal();
1201 if (!focusedPrincipal || !newPrincipal) {
1202 return;
1204 bool subsumes = false;
1205 focusedPrincipal->Subsumes(newPrincipal, &subsumes);
1206 if (!subsumes && !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
1207 NS_WARNING("Not allowed to focus the new window!");
1208 return;
1212 // to check if the new element is in the active window, compare the
1213 // new root docshell for the new element with the active window's docshell.
1214 bool isElementInActiveWindow = false;
1216 nsCOMPtr<nsIDocShellTreeItem> dsti = newWindow->GetDocShell();
1217 nsCOMPtr<nsPIDOMWindowOuter> newRootWindow;
1218 if (dsti) {
1219 nsCOMPtr<nsIDocShellTreeItem> root;
1220 dsti->GetRootTreeItem(getter_AddRefs(root));
1221 newRootWindow = root ? root->GetWindow() : nullptr;
1223 isElementInActiveWindow = (mActiveWindow && newRootWindow == mActiveWindow);
1226 // Exit fullscreen if we're focusing a windowed plugin on a non-MacOSX
1227 // system. We don't control event dispatch to windowed plugins on non-MacOSX,
1228 // so we can't display the "Press ESC to leave fullscreen mode" warning on
1229 // key input if a windowed plugin is focused, so just exit fullscreen
1230 // to guard against phishing.
1231 #ifndef XP_MACOSX
1232 if (elementToFocus &&
1233 nsContentUtils::GetRootDocument(elementToFocus->OwnerDoc())
1234 ->GetFullscreenElement() &&
1235 nsContentUtils::HasPluginWithUncontrolledEventDispatch(elementToFocus)) {
1236 nsContentUtils::ReportToConsole(
1237 nsIScriptError::warningFlag, NS_LITERAL_CSTRING("DOM"),
1238 elementToFocus->OwnerDoc(), nsContentUtils::eDOM_PROPERTIES,
1239 "FocusedWindowedPluginWhileFullscreen");
1240 Document::AsyncExitFullscreen(elementToFocus->OwnerDoc());
1242 #endif
1244 // if the FLAG_NOSWITCHFRAME flag is used, only allow the focus to be
1245 // shifted away from the current element if the new shell to focus is
1246 // the same or an ancestor shell of the currently focused shell.
1247 bool allowFrameSwitch = !(aFlags & FLAG_NOSWITCHFRAME) ||
1248 IsSameOrAncestor(newWindow, mFocusedWindow);
1250 // if the element is in the active window, frame switching is allowed and
1251 // the content is in a visible window, fire blur and focus events.
1252 bool sendFocusEvent =
1253 isElementInActiveWindow && allowFrameSwitch && IsWindowVisible(newWindow);
1255 // When the following conditions are true:
1256 // * an element has focus
1257 // * isn't called by trusted event (i.e., called by untrusted event or by js)
1258 // * the focus is moved to another document's element
1259 // we need to check the permission.
1260 if (sendFocusEvent && mFocusedElement &&
1261 !nsContentUtils::LegacyIsCallerNativeCode() &&
1262 mFocusedElement->OwnerDoc() != aNewContent->OwnerDoc()) {
1263 // If the caller cannot access the current focused node, the caller should
1264 // not be able to steal focus from it. E.g., When the current focused node
1265 // is in chrome, any web contents should not be able to steal the focus.
1266 sendFocusEvent = nsContentUtils::CanCallerAccess(mFocusedElement);
1267 if (!sendFocusEvent && mMouseButtonEventHandlingDocument) {
1268 // However, while mouse button event is handling, the handling document's
1269 // script should be able to steal focus.
1270 sendFocusEvent =
1271 nsContentUtils::CanCallerAccess(mMouseButtonEventHandlingDocument);
1275 LOGCONTENT("Shift Focus: %s", elementToFocus.get());
1276 LOGFOCUS((" Flags: %x Current Window: %p New Window: %p Current Element: %p",
1277 aFlags, mFocusedWindow.get(), newWindow.get(),
1278 mFocusedElement.get()));
1279 LOGFOCUS((" In Active Window: %d In Focused Window: %d SendFocus: %d",
1280 isElementInActiveWindow, isElementInFocusedWindow, sendFocusEvent));
1282 if (sendFocusEvent) {
1283 RefPtr<Element> oldFocusedElement = mFocusedElement;
1284 // return if blurring fails or the focus changes during the blur
1285 if (mFocusedWindow) {
1286 // if the focus is being moved to another element in the same document,
1287 // or to a descendant, pass the existing window to Blur so that the
1288 // current node in the existing window is cleared. If moving to a
1289 // window elsewhere, we want to maintain the current node in the
1290 // window but still blur it.
1291 bool currentIsSameOrAncestor =
1292 IsSameOrAncestor(mFocusedWindow, newWindow);
1293 // find the common ancestor of the currently focused window and the new
1294 // window. The ancestor will need to have its currently focused node
1295 // cleared once the document has been blurred. Otherwise, we'll be in a
1296 // state where a document is blurred yet the chain of windows above it
1297 // still points to that document.
1298 // For instance, in the following frame tree:
1299 // A
1300 // B C
1301 // D
1302 // D is focused and we want to focus C. Once D has been blurred, we need
1303 // to clear out the focus in A, otherwise A would still maintain that B
1304 // was focused, and B that D was focused.
1305 nsCOMPtr<nsPIDOMWindowOuter> commonAncestor;
1306 if (!isElementInFocusedWindow)
1307 commonAncestor = GetCommonAncestor(newWindow, mFocusedWindow);
1309 if (!Blur(currentIsSameOrAncestor ? mFocusedWindow.get() : nullptr,
1310 commonAncestor, !isElementInFocusedWindow, aAdjustWidget,
1311 elementToFocus)) {
1312 return;
1316 Focus(newWindow, elementToFocus, aFlags, !isElementInFocusedWindow,
1317 aFocusChanged, false, aAdjustWidget, oldFocusedElement);
1318 } else {
1319 // otherwise, for inactive windows and when the caller cannot steal the
1320 // focus, update the node in the window, and raise the window if desired.
1321 if (allowFrameSwitch) AdjustWindowFocus(newWindow, true);
1323 // set the focus node and method as needed
1324 uint32_t focusMethod =
1325 aFocusChanged ? aFlags & FOCUSMETHODANDRING_MASK
1326 : newWindow->GetFocusMethod() | (aFlags & FLAG_SHOWRING);
1327 newWindow->SetFocusedElement(elementToFocus, focusMethod);
1328 if (aFocusChanged) {
1329 nsCOMPtr<nsIDocShell> docShell = newWindow->GetDocShell();
1331 RefPtr<PresShell> presShell = docShell->GetPresShell();
1332 if (presShell && presShell->DidInitialize()) {
1333 ScrollIntoView(presShell, elementToFocus, aFlags);
1337 // update the commands even when inactive so that the attributes for that
1338 // window are up to date.
1339 if (allowFrameSwitch)
1340 newWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
1342 if (aFlags & FLAG_RAISE) RaiseWindow(newRootWindow);
1346 bool nsFocusManager::IsSameOrAncestor(nsPIDOMWindowOuter* aPossibleAncestor,
1347 nsPIDOMWindowOuter* aWindow) {
1348 if (!aWindow || !aPossibleAncestor) {
1349 return false;
1352 nsCOMPtr<nsIDocShellTreeItem> ancestordsti = aPossibleAncestor->GetDocShell();
1353 nsCOMPtr<nsIDocShellTreeItem> dsti = aWindow->GetDocShell();
1354 while (dsti) {
1355 if (dsti == ancestordsti) return true;
1356 nsCOMPtr<nsIDocShellTreeItem> parentDsti;
1357 dsti->GetParent(getter_AddRefs(parentDsti));
1358 dsti.swap(parentDsti);
1361 return false;
1364 already_AddRefed<nsPIDOMWindowOuter> nsFocusManager::GetCommonAncestor(
1365 nsPIDOMWindowOuter* aWindow1, nsPIDOMWindowOuter* aWindow2) {
1366 NS_ENSURE_TRUE(aWindow1 && aWindow2, nullptr);
1368 nsCOMPtr<nsIDocShellTreeItem> dsti1 = aWindow1->GetDocShell();
1369 NS_ENSURE_TRUE(dsti1, nullptr);
1371 nsCOMPtr<nsIDocShellTreeItem> dsti2 = aWindow2->GetDocShell();
1372 NS_ENSURE_TRUE(dsti2, nullptr);
1374 AutoTArray<nsIDocShellTreeItem*, 30> parents1, parents2;
1375 do {
1376 parents1.AppendElement(dsti1);
1377 nsCOMPtr<nsIDocShellTreeItem> parentDsti1;
1378 dsti1->GetParent(getter_AddRefs(parentDsti1));
1379 dsti1.swap(parentDsti1);
1380 } while (dsti1);
1381 do {
1382 parents2.AppendElement(dsti2);
1383 nsCOMPtr<nsIDocShellTreeItem> parentDsti2;
1384 dsti2->GetParent(getter_AddRefs(parentDsti2));
1385 dsti2.swap(parentDsti2);
1386 } while (dsti2);
1388 uint32_t pos1 = parents1.Length();
1389 uint32_t pos2 = parents2.Length();
1390 nsIDocShellTreeItem* parent = nullptr;
1391 uint32_t len;
1392 for (len = std::min(pos1, pos2); len > 0; --len) {
1393 nsIDocShellTreeItem* child1 = parents1.ElementAt(--pos1);
1394 nsIDocShellTreeItem* child2 = parents2.ElementAt(--pos2);
1395 if (child1 != child2) {
1396 break;
1398 parent = child1;
1401 nsCOMPtr<nsPIDOMWindowOuter> window = parent ? parent->GetWindow() : nullptr;
1402 return window.forget();
1405 void nsFocusManager::AdjustWindowFocus(nsPIDOMWindowOuter* aWindow,
1406 bool aCheckPermission) {
1407 bool isVisible = IsWindowVisible(aWindow);
1409 nsCOMPtr<nsPIDOMWindowOuter> window(aWindow);
1410 while (window) {
1411 // get the containing <iframe> or equivalent element so that it can be
1412 // focused below.
1413 nsCOMPtr<Element> frameElement = window->GetFrameElementInternal();
1415 nsCOMPtr<nsIDocShellTreeItem> dsti = window->GetDocShell();
1416 if (!dsti) return;
1417 nsCOMPtr<nsIDocShellTreeItem> parentDsti;
1418 dsti->GetParent(getter_AddRefs(parentDsti));
1419 if (!parentDsti) {
1420 return;
1423 window = parentDsti->GetWindow();
1424 if (window) {
1425 // if the parent window is visible but aWindow was not, then we have
1426 // likely moved up and out from a hidden tab to the browser window, or a
1427 // similar such arrangement. Stop adjusting the current nodes.
1428 if (IsWindowVisible(window) != isVisible) break;
1430 // When aCheckPermission is true, we should check whether the caller can
1431 // access the window or not. If it cannot access, we should stop the
1432 // adjusting.
1433 if (aCheckPermission && !nsContentUtils::LegacyIsCallerNativeCode() &&
1434 !nsContentUtils::CanCallerAccess(window->GetCurrentInnerWindow())) {
1435 break;
1438 window->SetFocusedElement(frameElement);
1443 bool nsFocusManager::IsWindowVisible(nsPIDOMWindowOuter* aWindow) {
1444 if (!aWindow || aWindow->IsFrozen()) return false;
1446 // Check if the inner window is frozen as well. This can happen when a focus
1447 // change occurs while restoring a previous page.
1448 nsPIDOMWindowInner* innerWindow = aWindow->GetCurrentInnerWindow();
1449 if (!innerWindow || innerWindow->IsFrozen()) return false;
1451 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
1452 nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(docShell));
1453 if (!baseWin) return false;
1455 bool visible = false;
1456 baseWin->GetVisibility(&visible);
1457 return visible;
1460 bool nsFocusManager::IsNonFocusableRoot(nsIContent* aContent) {
1461 MOZ_ASSERT(aContent, "aContent must not be NULL");
1462 MOZ_ASSERT(aContent->IsInComposedDoc(), "aContent must be in a document");
1464 // If aContent is in designMode, the root element is not focusable.
1465 // NOTE: in designMode, most elements are not focusable, just the document is
1466 // focusable.
1467 // Also, if aContent is not editable but it isn't in designMode, it's not
1468 // focusable.
1469 // And in userfocusignored context nothing is focusable.
1470 Document* doc = aContent->GetComposedDoc();
1471 NS_ASSERTION(doc, "aContent must have current document");
1472 return aContent == doc->GetRootElement() &&
1473 (doc->HasFlag(NODE_IS_EDITABLE) || !aContent->IsEditable() ||
1474 nsContentUtils::IsUserFocusIgnored(aContent));
1477 Element* nsFocusManager::FlushAndCheckIfFocusable(Element* aElement,
1478 uint32_t aFlags) {
1479 if (!aElement) return nullptr;
1481 nsCOMPtr<Document> doc = aElement->GetComposedDoc();
1482 // can't focus elements that are not in documents
1483 if (!doc) {
1484 LOGCONTENT("Cannot focus %s because content not in document", aElement)
1485 return nullptr;
1488 // Make sure that our frames are up to date while ensuring the presshell is
1489 // also initialized in case we come from a script calling focus() early.
1490 mEventHandlingNeedsFlush = false;
1491 doc->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames);
1493 // this is a special case for some XUL elements or input number, where an
1494 // anonymous child is actually focusable and not the element itself.
1495 RefPtr<Element> redirectedFocus = GetRedirectedFocus(aElement);
1496 if (redirectedFocus) {
1497 return FlushAndCheckIfFocusable(redirectedFocus, aFlags);
1500 PresShell* presShell = doc->GetPresShell();
1501 if (!presShell) {
1502 return nullptr;
1505 // the root content can always be focused,
1506 // except in userfocusignored context.
1507 if (aElement == doc->GetRootElement()) {
1508 return nsContentUtils::IsUserFocusIgnored(aElement) ? nullptr : aElement;
1511 // cannot focus content in print preview mode. Only the root can be focused.
1512 nsPresContext* presContext = presShell->GetPresContext();
1513 if (presContext &&
1514 presContext->Type() == nsPresContext::eContext_PrintPreview) {
1515 LOGCONTENT("Cannot focus %s while in print preview", aElement)
1516 return nullptr;
1519 nsIFrame* frame = aElement->GetPrimaryFrame();
1520 if (!frame) {
1521 LOGCONTENT("Cannot focus %s as it has no frame", aElement)
1522 return nullptr;
1525 if (aElement->IsHTMLElement(nsGkAtoms::area)) {
1526 // HTML areas do not have their own frame, and the img frame we get from
1527 // GetPrimaryFrame() is not relevant as to whether it is focusable or
1528 // not, so we have to do all the relevant checks manually for them.
1529 return frame->IsVisibleConsideringAncestors() && aElement->IsFocusable()
1530 ? aElement
1531 : nullptr;
1534 // if this is a child frame content node, check if it is visible and
1535 // call the content node's IsFocusable method instead of the frame's
1536 // IsFocusable method. This skips checking the style system and ensures that
1537 // offscreen browsers can still be focused.
1538 Document* subdoc = doc->GetSubDocumentFor(aElement);
1539 if (subdoc && IsWindowVisible(subdoc->GetWindow())) {
1540 const nsStyleUI* ui = frame->StyleUI();
1541 int32_t tabIndex = (ui->mUserFocus == StyleUserFocus::Ignore ||
1542 ui->mUserFocus == StyleUserFocus::None)
1543 ? -1
1544 : 0;
1545 return aElement->IsFocusable(&tabIndex, aFlags & FLAG_BYMOUSE) ? aElement
1546 : nullptr;
1549 return frame->IsFocusable(nullptr, aFlags & FLAG_BYMOUSE) ? aElement
1550 : nullptr;
1553 bool nsFocusManager::Blur(nsPIDOMWindowOuter* aWindowToClear,
1554 nsPIDOMWindowOuter* aAncestorWindowToFocus,
1555 bool aIsLeavingDocument, bool aAdjustWidgets,
1556 nsIContent* aContentToFocus) {
1557 LOGFOCUS(("<<Blur begin>>"));
1559 // hold a reference to the focused content, which may be null
1560 RefPtr<Element> element = mFocusedElement;
1561 if (element) {
1562 if (!element->IsInComposedDoc()) {
1563 mFocusedElement = nullptr;
1564 return true;
1566 if (element == mFirstBlurEvent) return true;
1569 // hold a reference to the focused window
1570 nsCOMPtr<nsPIDOMWindowOuter> window = mFocusedWindow;
1571 if (!window) {
1572 mFocusedElement = nullptr;
1573 return true;
1576 nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
1577 if (!docShell) {
1578 mFocusedWindow = nullptr;
1579 mFocusedElement = nullptr;
1580 return true;
1583 // Keep a ref to presShell since dispatching the DOM event may cause
1584 // the document to be destroyed.
1585 RefPtr<PresShell> presShell = docShell->GetPresShell();
1586 if (!presShell) {
1587 mFocusedElement = nullptr;
1588 mFocusedWindow = nullptr;
1589 return true;
1592 bool clearFirstBlurEvent = false;
1593 if (!mFirstBlurEvent) {
1594 mFirstBlurEvent = element;
1595 clearFirstBlurEvent = true;
1598 nsPresContext* focusedPresContext =
1599 mActiveWindow ? presShell->GetPresContext() : nullptr;
1600 IMEStateManager::OnChangeFocus(focusedPresContext, nullptr,
1601 GetFocusMoveActionCause(0));
1603 // now adjust the actual focus, by clearing the fields in the focus manager
1604 // and in the window.
1605 mFocusedElement = nullptr;
1606 bool shouldShowFocusRing = window->ShouldShowFocusRing();
1607 if (aWindowToClear) aWindowToClear->SetFocusedElement(nullptr);
1609 LOGCONTENT("Element %s has been blurred", element.get());
1611 // Don't fire blur event on the root content which isn't editable.
1612 bool sendBlurEvent =
1613 element && element->IsInComposedDoc() && !IsNonFocusableRoot(element);
1614 if (element) {
1615 if (sendBlurEvent) {
1616 NotifyFocusStateChange(element, aContentToFocus, shouldShowFocusRing,
1617 false);
1620 // if an object/plug-in/remote browser is being blurred, move the system
1621 // focus to the parent window, otherwise events will still get fired at the
1622 // plugin. But don't do this if we are blurring due to the window being
1623 // lowered, otherwise, the parent window can get raised again.
1624 if (mActiveWindow) {
1625 nsIFrame* contentFrame = element->GetPrimaryFrame();
1626 nsIObjectFrame* objectFrame = do_QueryFrame(contentFrame);
1627 if (aAdjustWidgets && objectFrame && !sTestMode) {
1628 if (XRE_IsContentProcess()) {
1629 // set focus to the top level window via the chrome process.
1630 nsCOMPtr<nsIBrowserChild> browserChild = docShell->GetBrowserChild();
1631 if (browserChild) {
1632 static_cast<BrowserChild*>(browserChild.get())
1633 ->SendDispatchFocusToTopLevelWindow();
1635 } else {
1636 // note that the presshell's widget is being retrieved here, not the
1637 // one for the object frame.
1638 if (nsViewManager* vm = presShell->GetViewManager()) {
1639 nsCOMPtr<nsIWidget> widget;
1640 vm->GetRootWidget(getter_AddRefs(widget));
1641 if (widget) {
1642 // set focus to the top level window but don't raise it.
1643 widget->SetFocus(nsIWidget::Raise::No);
1650 // if the object being blurred is a remote browser, deactivate remote
1651 // content
1652 if (BrowserParent* remote = BrowserParent::GetFrom(element)) {
1653 remote->Deactivate();
1654 LOGFOCUS(("Remote browser deactivated %p", remote));
1657 // Same as above but for out-of-process iframes
1658 if (BrowserBridgeChild* bbc = BrowserBridgeChild::GetFrom(element)) {
1659 bbc->Deactivate();
1660 LOGFOCUS(("Out-of-process iframe deactivated %p", bbc));
1664 bool result = true;
1665 if (sendBlurEvent) {
1666 // if there is an active window, update commands. If there isn't an active
1667 // window, then this was a blur caused by the active window being lowered,
1668 // so there is no need to update the commands
1669 if (mActiveWindow)
1670 window->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
1672 SendFocusOrBlurEvent(eBlur, presShell, element->GetComposedDoc(), element,
1673 1, false, false, aContentToFocus);
1676 // if we are leaving the document or the window was lowered, make the caret
1677 // invisible.
1678 if (aIsLeavingDocument || !mActiveWindow) {
1679 SetCaretVisible(presShell, false, nullptr);
1682 RefPtr<AccessibleCaretEventHub> eventHub =
1683 presShell->GetAccessibleCaretEventHub();
1684 if (eventHub) {
1685 eventHub->NotifyBlur(aIsLeavingDocument || !mActiveWindow);
1688 // at this point, it is expected that this window will be still be
1689 // focused, but the focused element will be null, as it was cleared before
1690 // the event. If this isn't the case, then something else was focused during
1691 // the blur event above and we should just return. However, if
1692 // aIsLeavingDocument is set, a new document is desired, so make sure to
1693 // blur the document and window.
1694 if (mFocusedWindow != window ||
1695 (mFocusedElement != nullptr && !aIsLeavingDocument)) {
1696 result = false;
1697 } else if (aIsLeavingDocument) {
1698 window->TakeFocus(false, 0);
1700 // clear the focus so that the ancestor frame hierarchy is in the correct
1701 // state. Pass true because aAncestorWindowToFocus is thought to be
1702 // focused at this point.
1703 if (aAncestorWindowToFocus)
1704 aAncestorWindowToFocus->SetFocusedElement(nullptr, 0, true);
1706 SetFocusedWindowInternal(nullptr);
1707 mFocusedElement = nullptr;
1709 // pass 1 for the focus method when calling SendFocusOrBlurEvent just so
1710 // that the check is made for suppressed documents. Check to ensure that
1711 // the document isn't null in case someone closed it during the blur above
1712 Document* doc = window->GetExtantDoc();
1713 if (doc)
1714 SendFocusOrBlurEvent(eBlur, presShell, doc, ToSupports(doc), 1, false);
1715 if (mFocusedWindow == nullptr)
1716 SendFocusOrBlurEvent(eBlur, presShell, doc,
1717 window->GetCurrentInnerWindow(), 1, false);
1719 // check if a different window was focused
1720 result = (mFocusedWindow == nullptr && mActiveWindow);
1721 } else if (mActiveWindow) {
1722 // Otherwise, the blur of the element without blurring the document
1723 // occurred normally. Call UpdateCaret to redisplay the caret at the right
1724 // location within the document. This is needed to ensure that the caret
1725 // used for caret browsing is made visible again when an input field is
1726 // blurred.
1727 UpdateCaret(false, true, nullptr);
1730 if (clearFirstBlurEvent) mFirstBlurEvent = nullptr;
1732 return result;
1735 void nsFocusManager::ActivateRemoteFrameIfNeeded(Element& aElement) {
1736 MOZ_DIAGNOSTIC_ASSERT(mFocusedElement == &aElement);
1737 if (BrowserParent* remote = BrowserParent::GetFrom(&aElement)) {
1738 remote->Activate();
1739 LOGFOCUS(("Remote browser activated %p", remote));
1742 // Same as above but for out-of-process iframes
1743 if (BrowserBridgeChild* bbc = BrowserBridgeChild::GetFrom(&aElement)) {
1744 bbc->Activate();
1745 LOGFOCUS(("Out-of-process iframe activated %p", bbc));
1749 void nsFocusManager::Focus(nsPIDOMWindowOuter* aWindow, Element* aElement,
1750 uint32_t aFlags, bool aIsNewDocument,
1751 bool aFocusChanged, bool aWindowRaised,
1752 bool aAdjustWidgets, nsIContent* aContentLostFocus) {
1753 LOGFOCUS(("<<Focus begin>>"));
1755 if (!aWindow) return;
1757 if (aElement && (aElement == mFirstFocusEvent || aElement == mFirstBlurEvent))
1758 return;
1760 // Keep a reference to the presShell since dispatching the DOM event may
1761 // cause the document to be destroyed.
1762 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
1763 if (!docShell) return;
1765 RefPtr<PresShell> presShell = docShell->GetPresShell();
1766 if (!presShell) {
1767 return;
1770 // If the focus actually changed, set the focus method (mouse, keyboard, etc).
1771 // Otherwise, just get the current focus method and use that. This ensures
1772 // that the method is set during the document and window focus events.
1773 uint32_t focusMethod =
1774 aFocusChanged ? aFlags & FOCUSMETHODANDRING_MASK
1775 : aWindow->GetFocusMethod() | (aFlags & FLAG_SHOWRING);
1777 if (!IsWindowVisible(aWindow)) {
1778 // if the window isn't visible, for instance because it is a hidden tab,
1779 // update the current focus and scroll it into view but don't do anything
1780 // else
1781 if (FlushAndCheckIfFocusable(aElement, aFlags)) {
1782 aWindow->SetFocusedElement(aElement, focusMethod);
1783 if (aFocusChanged) {
1784 ScrollIntoView(presShell, aElement, aFlags);
1787 return;
1790 bool clearFirstFocusEvent = false;
1791 if (!mFirstFocusEvent) {
1792 mFirstFocusEvent = aElement;
1793 clearFirstFocusEvent = true;
1796 LOGCONTENT("Element %s has been focused", aElement);
1798 if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
1799 Document* docm = aWindow->GetExtantDoc();
1800 if (docm) {
1801 LOGCONTENT(" from %s", docm->GetRootElement());
1803 LOGFOCUS((" [Newdoc: %d FocusChanged: %d Raised: %d Flags: %x]",
1804 aIsNewDocument, aFocusChanged, aWindowRaised, aFlags));
1807 if (aIsNewDocument) {
1808 // if this is a new document, update the parent chain of frames so that
1809 // focus can be traversed from the top level down to the newly focused
1810 // window.
1811 AdjustWindowFocus(aWindow, false);
1814 // indicate that the window has taken focus.
1815 if (aWindow->TakeFocus(true, focusMethod)) aIsNewDocument = true;
1817 SetFocusedWindowInternal(aWindow);
1819 // Update the system focus by focusing the root widget. But avoid this
1820 // if 1) aAdjustWidgets is false or 2) aElement is a plugin that has its
1821 // own widget and is either already focused or is about to be focused.
1822 nsCOMPtr<nsIWidget> objectFrameWidget;
1823 if (aElement) {
1824 nsIFrame* contentFrame = aElement->GetPrimaryFrame();
1825 nsIObjectFrame* objectFrame = do_QueryFrame(contentFrame);
1826 if (objectFrame) objectFrameWidget = objectFrame->GetWidget();
1828 if (aAdjustWidgets && !objectFrameWidget && !sTestMode) {
1829 if (nsViewManager* vm = presShell->GetViewManager()) {
1830 nsCOMPtr<nsIWidget> widget;
1831 vm->GetRootWidget(getter_AddRefs(widget));
1832 if (widget) widget->SetFocus(nsIWidget::Raise::No);
1836 // if switching to a new document, first fire the focus event on the
1837 // document and then the window.
1838 if (aIsNewDocument) {
1839 Document* doc = aWindow->GetExtantDoc();
1840 // The focus change should be notified to IMEStateManager from here if
1841 // the focused element is a designMode editor since any content won't
1842 // receive focus event.
1843 if (doc && doc->HasFlag(NODE_IS_EDITABLE)) {
1844 IMEStateManager::OnChangeFocus(presShell->GetPresContext(), nullptr,
1845 GetFocusMoveActionCause(aFlags));
1847 if (doc) {
1848 SendFocusOrBlurEvent(eFocus, presShell, doc, ToSupports(doc),
1849 aFlags & FOCUSMETHOD_MASK, aWindowRaised);
1851 if (mFocusedWindow == aWindow && mFocusedElement == nullptr) {
1852 SendFocusOrBlurEvent(eFocus, presShell, doc,
1853 aWindow->GetCurrentInnerWindow(),
1854 aFlags & FOCUSMETHOD_MASK, aWindowRaised);
1858 // check to ensure that the element is still focusable, and that nothing
1859 // else was focused during the events above.
1860 if (FlushAndCheckIfFocusable(aElement, aFlags) && mFocusedWindow == aWindow &&
1861 mFocusedElement == nullptr) {
1862 mFocusedElement = aElement;
1864 nsIContent* focusedNode = aWindow->GetFocusedElement();
1865 bool isRefocus = focusedNode && focusedNode->IsEqualNode(aElement);
1867 aWindow->SetFocusedElement(aElement, focusMethod);
1869 // if the focused element changed, scroll it into view
1870 if (aElement && aFocusChanged) {
1871 ScrollIntoView(presShell, aElement, aFlags);
1874 bool sendFocusEvent = aElement && aElement->IsInComposedDoc() &&
1875 !IsNonFocusableRoot(aElement);
1876 nsPresContext* presContext = presShell->GetPresContext();
1877 if (sendFocusEvent) {
1878 NotifyFocusStateChange(aElement, nullptr, aWindow->ShouldShowFocusRing(),
1879 true);
1881 // if this is an object/plug-in/remote browser, focus its widget. Note
1882 // that we might no longer be in the same document, due to the events we
1883 // fired above when aIsNewDocument.
1884 if (presShell->GetDocument() == aElement->GetComposedDoc()) {
1885 if (aAdjustWidgets && objectFrameWidget && !sTestMode) {
1886 objectFrameWidget->SetFocus(nsIWidget::Raise::No);
1889 // if the object being focused is a remote browser, activate remote
1890 // content
1891 ActivateRemoteFrameIfNeeded(*aElement);
1894 IMEStateManager::OnChangeFocus(presContext, aElement,
1895 GetFocusMoveActionCause(aFlags));
1897 // as long as this focus wasn't because a window was raised, update the
1898 // commands
1899 // XXXndeakin P2 someone could adjust the focus during the update
1900 if (!aWindowRaised)
1901 aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
1903 SendFocusOrBlurEvent(eFocus, presShell, aElement->GetComposedDoc(),
1904 aElement, aFlags & FOCUSMETHOD_MASK, aWindowRaised,
1905 isRefocus, aContentLostFocus);
1906 } else {
1907 IMEStateManager::OnChangeFocus(presContext, nullptr,
1908 GetFocusMoveActionCause(aFlags));
1909 if (!aWindowRaised) {
1910 aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
1913 } else {
1914 // If the window focus event (fired above when aIsNewDocument) caused
1915 // the plugin not to be focusable, update the system focus by focusing
1916 // the root widget.
1917 if (aAdjustWidgets && objectFrameWidget && mFocusedWindow == aWindow &&
1918 mFocusedElement == nullptr && !sTestMode) {
1919 if (nsViewManager* vm = presShell->GetViewManager()) {
1920 nsCOMPtr<nsIWidget> widget;
1921 vm->GetRootWidget(getter_AddRefs(widget));
1922 if (widget) {
1923 widget->SetFocus(nsIWidget::Raise::No);
1928 if (!mFocusedElement) {
1929 // When there is no focused element, IMEStateManager needs to adjust IME
1930 // enabled state with the document.
1931 nsPresContext* presContext = presShell->GetPresContext();
1932 IMEStateManager::OnChangeFocus(presContext, nullptr,
1933 GetFocusMoveActionCause(aFlags));
1936 if (!aWindowRaised)
1937 aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
1940 // update the caret visibility and position to match the newly focused
1941 // element. However, don't update the position if this was a focus due to a
1942 // mouse click as the selection code would already have moved the caret as
1943 // needed. If this is a different document than was focused before, also
1944 // update the caret's visibility. If this is the same document, the caret
1945 // visibility should be the same as before so there is no need to update it.
1946 if (mFocusedElement == aElement)
1947 UpdateCaret(aFocusChanged && !(aFlags & FLAG_BYMOUSE), aIsNewDocument,
1948 mFocusedElement);
1950 if (clearFirstFocusEvent) mFirstFocusEvent = nullptr;
1953 class FocusBlurEvent : public Runnable {
1954 public:
1955 FocusBlurEvent(nsISupports* aTarget, EventMessage aEventMessage,
1956 nsPresContext* aContext, bool aWindowRaised, bool aIsRefocus,
1957 EventTarget* aRelatedTarget)
1958 : mozilla::Runnable("FocusBlurEvent"),
1959 mTarget(aTarget),
1960 mContext(aContext),
1961 mEventMessage(aEventMessage),
1962 mWindowRaised(aWindowRaised),
1963 mIsRefocus(aIsRefocus),
1964 mRelatedTarget(aRelatedTarget) {}
1966 NS_IMETHOD Run() override {
1967 InternalFocusEvent event(true, mEventMessage);
1968 event.mFlags.mBubbles = false;
1969 event.mFlags.mCancelable = false;
1970 event.mFromRaise = mWindowRaised;
1971 event.mIsRefocus = mIsRefocus;
1972 event.mRelatedTarget = mRelatedTarget;
1973 return EventDispatcher::Dispatch(mTarget, mContext, &event);
1976 nsCOMPtr<nsISupports> mTarget;
1977 RefPtr<nsPresContext> mContext;
1978 EventMessage mEventMessage;
1979 bool mWindowRaised;
1980 bool mIsRefocus;
1981 nsCOMPtr<EventTarget> mRelatedTarget;
1984 class FocusInOutEvent : public Runnable {
1985 public:
1986 FocusInOutEvent(nsISupports* aTarget, EventMessage aEventMessage,
1987 nsPresContext* aContext,
1988 nsPIDOMWindowOuter* aOriginalFocusedWindow,
1989 nsIContent* aOriginalFocusedContent,
1990 EventTarget* aRelatedTarget)
1991 : mozilla::Runnable("FocusInOutEvent"),
1992 mTarget(aTarget),
1993 mContext(aContext),
1994 mEventMessage(aEventMessage),
1995 mOriginalFocusedWindow(aOriginalFocusedWindow),
1996 mOriginalFocusedContent(aOriginalFocusedContent),
1997 mRelatedTarget(aRelatedTarget) {}
1999 NS_IMETHOD Run() override {
2000 nsCOMPtr<nsIContent> originalWindowFocus =
2001 mOriginalFocusedWindow ? mOriginalFocusedWindow->GetFocusedElement()
2002 : nullptr;
2003 // Blink does not check that focus is the same after blur, but WebKit does.
2004 // Opt to follow Blink's behavior (see bug 687787).
2005 if (mEventMessage == eFocusOut ||
2006 originalWindowFocus == mOriginalFocusedContent) {
2007 InternalFocusEvent event(true, mEventMessage);
2008 event.mFlags.mBubbles = true;
2009 event.mFlags.mCancelable = false;
2010 event.mRelatedTarget = mRelatedTarget;
2011 return EventDispatcher::Dispatch(mTarget, mContext, &event);
2013 return NS_OK;
2016 nsCOMPtr<nsISupports> mTarget;
2017 RefPtr<nsPresContext> mContext;
2018 EventMessage mEventMessage;
2019 nsCOMPtr<nsPIDOMWindowOuter> mOriginalFocusedWindow;
2020 nsCOMPtr<nsIContent> mOriginalFocusedContent;
2021 nsCOMPtr<EventTarget> mRelatedTarget;
2024 static Document* GetDocumentHelper(EventTarget* aTarget) {
2025 nsCOMPtr<nsINode> node = do_QueryInterface(aTarget);
2026 if (!node) {
2027 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aTarget);
2028 return win ? win->GetExtantDoc() : nullptr;
2031 return node->OwnerDoc();
2034 void nsFocusManager::FireFocusInOrOutEvent(
2035 EventMessage aEventMessage, PresShell* aPresShell, nsISupports* aTarget,
2036 nsPIDOMWindowOuter* aCurrentFocusedWindow,
2037 nsIContent* aCurrentFocusedContent, EventTarget* aRelatedTarget) {
2038 NS_ASSERTION(aEventMessage == eFocusIn || aEventMessage == eFocusOut,
2039 "Wrong event type for FireFocusInOrOutEvent");
2041 nsContentUtils::AddScriptRunner(new FocusInOutEvent(
2042 aTarget, aEventMessage, aPresShell->GetPresContext(),
2043 aCurrentFocusedWindow, aCurrentFocusedContent, aRelatedTarget));
2046 void nsFocusManager::SendFocusOrBlurEvent(
2047 EventMessage aEventMessage, PresShell* aPresShell, Document* aDocument,
2048 nsISupports* aTarget, uint32_t aFocusMethod, bool aWindowRaised,
2049 bool aIsRefocus, EventTarget* aRelatedTarget) {
2050 NS_ASSERTION(aEventMessage == eFocus || aEventMessage == eBlur,
2051 "Wrong event type for SendFocusOrBlurEvent");
2053 nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aTarget);
2054 nsCOMPtr<Document> eventTargetDoc = GetDocumentHelper(eventTarget);
2055 nsCOMPtr<Document> relatedTargetDoc = GetDocumentHelper(aRelatedTarget);
2057 // set aRelatedTarget to null if it's not in the same document as eventTarget
2058 if (eventTargetDoc != relatedTargetDoc) {
2059 aRelatedTarget = nullptr;
2062 bool dontDispatchEvent =
2063 eventTargetDoc && nsContentUtils::IsUserFocusIgnored(eventTargetDoc);
2065 if (!dontDispatchEvent && aDocument && aDocument->EventHandlingSuppressed()) {
2066 for (uint32_t i = mDelayedBlurFocusEvents.Length(); i > 0; --i) {
2067 // if this event was already queued, remove it and append it to the end
2068 if (mDelayedBlurFocusEvents[i - 1].mEventMessage == aEventMessage &&
2069 mDelayedBlurFocusEvents[i - 1].mPresShell == aPresShell &&
2070 mDelayedBlurFocusEvents[i - 1].mDocument == aDocument &&
2071 mDelayedBlurFocusEvents[i - 1].mTarget == eventTarget &&
2072 mDelayedBlurFocusEvents[i - 1].mRelatedTarget == aRelatedTarget) {
2073 mDelayedBlurFocusEvents.RemoveElementAt(i - 1);
2077 mDelayedBlurFocusEvents.AppendElement(nsDelayedBlurOrFocusEvent(
2078 aEventMessage, aPresShell, aDocument, eventTarget, aRelatedTarget));
2079 return;
2082 // If mDelayedBlurFocusEvents queue is not empty, check if there are events
2083 // that belongs to this doc, if yes, fire them first.
2084 if (aDocument && !aDocument->EventHandlingSuppressed() &&
2085 mDelayedBlurFocusEvents.Length()) {
2086 FireDelayedEvents(aDocument);
2089 FireFocusOrBlurEvent(aEventMessage, aPresShell, aTarget, aWindowRaised,
2090 aIsRefocus, aRelatedTarget);
2093 void nsFocusManager::FireFocusOrBlurEvent(EventMessage aEventMessage,
2094 PresShell* aPresShell,
2095 nsISupports* aTarget,
2096 bool aWindowRaised, bool aIsRefocus,
2097 EventTarget* aRelatedTarget) {
2098 nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aTarget);
2099 nsCOMPtr<Document> eventTargetDoc = GetDocumentHelper(eventTarget);
2100 nsCOMPtr<nsPIDOMWindowOuter> currentWindow = mFocusedWindow;
2101 nsCOMPtr<nsPIDOMWindowInner> targetWindow = do_QueryInterface(aTarget);
2102 nsCOMPtr<Document> targetDocument = do_QueryInterface(aTarget);
2103 nsCOMPtr<nsIContent> currentFocusedContent =
2104 currentWindow ? currentWindow->GetFocusedElement() : nullptr;
2106 bool dontDispatchEvent =
2107 eventTargetDoc && nsContentUtils::IsUserFocusIgnored(eventTargetDoc);
2109 #ifdef ACCESSIBILITY
2110 nsAccessibilityService* accService = GetAccService();
2111 if (accService) {
2112 if (aEventMessage == eFocus) {
2113 accService->NotifyOfDOMFocus(aTarget);
2114 } else {
2115 accService->NotifyOfDOMBlur(aTarget);
2118 #endif
2120 if (!dontDispatchEvent) {
2121 nsContentUtils::AddScriptRunner(
2122 new FocusBlurEvent(aTarget, aEventMessage, aPresShell->GetPresContext(),
2123 aWindowRaised, aIsRefocus, aRelatedTarget));
2125 // Check that the target is not a window or document before firing
2126 // focusin/focusout. Other browsers do not fire focusin/focusout on window,
2127 // despite being required in the spec, so follow their behavior.
2129 // As for document, we should not even fire focus/blur, but until then, we
2130 // need this check. targetDocument should be removed once bug 1228802 is
2131 // resolved.
2132 if (!targetWindow && !targetDocument) {
2133 EventMessage focusInOrOutMessage =
2134 aEventMessage == eFocus ? eFocusIn : eFocusOut;
2135 FireFocusInOrOutEvent(focusInOrOutMessage, aPresShell, aTarget,
2136 currentWindow, currentFocusedContent,
2137 aRelatedTarget);
2142 void nsFocusManager::ScrollIntoView(PresShell* aPresShell, nsIContent* aContent,
2143 uint32_t aFlags) {
2144 // if the noscroll flag isn't set, scroll the newly focused element into view
2145 if (!(aFlags & FLAG_NOSCROLL)) {
2146 ScrollFlags scrollFlags = ScrollFlags::ScrollOverflowHidden;
2147 if (!(aFlags & FLAG_BYELEMENTFOCUS)) {
2148 scrollFlags |= ScrollFlags::IgnoreMarginAndPadding;
2150 aPresShell->ScrollContentIntoView(
2151 aContent, ScrollAxis(kScrollMinimum, WhenToScroll::IfNotVisible),
2152 ScrollAxis(kScrollMinimum, WhenToScroll::IfNotVisible), scrollFlags);
2156 void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow) {
2157 // don't raise windows that are already raised or are in the process of
2158 // being lowered
2159 if (!aWindow || aWindow == mActiveWindow || aWindow == mWindowBeingLowered)
2160 return;
2162 if (sTestMode) {
2163 // In test mode, emulate the existing window being lowered and the new
2164 // window being raised. This happens in a separate runnable to avoid
2165 // touching multiple windows in the current runnable.
2166 nsCOMPtr<nsPIDOMWindowOuter> active(mActiveWindow);
2167 nsCOMPtr<nsPIDOMWindowOuter> window(aWindow);
2168 RefPtr<nsFocusManager> self(this);
2169 NS_DispatchToCurrentThread(NS_NewRunnableFunction(
2170 "nsFocusManager::RaiseWindow", [self, active, window]() -> void {
2171 if (active) {
2172 self->WindowLowered(active);
2174 self->WindowRaised(window);
2175 }));
2176 return;
2179 #if defined(XP_WIN)
2180 // Windows would rather we focus the child widget, otherwise, the toplevel
2181 // widget will always end up being focused. Fortunately, focusing the child
2182 // widget will also have the effect of raising the window this widget is in.
2183 // But on other platforms, we can just focus the toplevel widget to raise
2184 // the window.
2185 nsCOMPtr<nsPIDOMWindowOuter> childWindow;
2186 GetFocusedDescendant(aWindow, eIncludeAllDescendants,
2187 getter_AddRefs(childWindow));
2188 if (!childWindow) childWindow = aWindow;
2190 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
2191 if (!docShell) return;
2193 PresShell* presShell = docShell->GetPresShell();
2194 if (!presShell) {
2195 return;
2198 if (nsViewManager* vm = presShell->GetViewManager()) {
2199 nsCOMPtr<nsIWidget> widget;
2200 vm->GetRootWidget(getter_AddRefs(widget));
2201 if (widget) widget->SetFocus(nsIWidget::Raise::Yes);
2203 #else
2204 nsCOMPtr<nsIBaseWindow> treeOwnerAsWin =
2205 do_QueryInterface(aWindow->GetDocShell());
2206 if (treeOwnerAsWin) {
2207 nsCOMPtr<nsIWidget> widget;
2208 treeOwnerAsWin->GetMainWidget(getter_AddRefs(widget));
2209 if (widget) widget->SetFocus(nsIWidget::Raise::Yes);
2211 #endif
2214 void nsFocusManager::UpdateCaretForCaretBrowsingMode() {
2215 UpdateCaret(false, true, mFocusedElement);
2218 void nsFocusManager::UpdateCaret(bool aMoveCaretToFocus, bool aUpdateVisibility,
2219 nsIContent* aContent) {
2220 LOGFOCUS(("Update Caret: %d %d", aMoveCaretToFocus, aUpdateVisibility));
2222 if (!mFocusedWindow) return;
2224 // this is called when a document is focused or when the caretbrowsing
2225 // preference is changed
2226 nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
2227 if (!focusedDocShell) {
2228 return;
2231 if (focusedDocShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
2232 return; // Never browse with caret in chrome
2235 bool browseWithCaret = Preferences::GetBool("accessibility.browsewithcaret");
2237 RefPtr<PresShell> presShell = focusedDocShell->GetPresShell();
2238 if (!presShell) {
2239 return;
2242 // If this is an editable document which isn't contentEditable, or a
2243 // contentEditable document and the node to focus is contentEditable,
2244 // return, so that we don't mess with caret visibility.
2245 bool isEditable = false;
2246 focusedDocShell->GetEditable(&isEditable);
2248 if (isEditable) {
2249 Document* doc = presShell->GetDocument();
2251 bool isContentEditableDoc =
2252 doc &&
2253 doc->GetEditingState() == Document::EditingState::eContentEditable;
2255 bool isFocusEditable = aContent && aContent->HasFlag(NODE_IS_EDITABLE);
2256 if (!isContentEditableDoc || isFocusEditable) return;
2259 if (!isEditable && aMoveCaretToFocus) {
2260 MoveCaretToFocus(presShell, aContent);
2263 if (!aUpdateVisibility) return;
2265 // XXXndeakin this doesn't seem right. It should be checking for this only
2266 // on the nearest ancestor frame which is a chrome frame. But this is
2267 // what the existing code does, so just leave it for now.
2268 if (!browseWithCaret) {
2269 nsCOMPtr<Element> docElement = mFocusedWindow->GetFrameElementInternal();
2270 if (docElement)
2271 browseWithCaret =
2272 docElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::showcaret,
2273 NS_LITERAL_STRING("true"), eCaseMatters);
2276 SetCaretVisible(presShell, browseWithCaret, aContent);
2279 void nsFocusManager::MoveCaretToFocus(PresShell* aPresShell,
2280 nsIContent* aContent) {
2281 nsCOMPtr<Document> doc = aPresShell->GetDocument();
2282 if (doc) {
2283 RefPtr<nsFrameSelection> frameSelection = aPresShell->FrameSelection();
2284 RefPtr<Selection> domSelection =
2285 frameSelection->GetSelection(SelectionType::eNormal);
2286 if (domSelection) {
2287 // First clear the selection. This way, if there is no currently focused
2288 // content, the selection will just be cleared.
2289 domSelection->RemoveAllRanges(IgnoreErrors());
2290 if (aContent) {
2291 ErrorResult rv;
2292 RefPtr<nsRange> newRange = doc->CreateRange(rv);
2293 if (NS_WARN_IF(rv.Failed())) {
2294 rv.SuppressException();
2295 return;
2298 // Set the range to the start of the currently focused node
2299 // Make sure it's collapsed
2300 newRange->SelectNodeContents(*aContent, IgnoreErrors());
2302 if (!aContent->GetFirstChild() ||
2303 aContent->IsNodeOfType(nsINode::eHTML_FORM_CONTROL)) {
2304 // If current focus node is a leaf, set range to before the
2305 // node by using the parent as a container.
2306 // This prevents it from appearing as selected.
2307 newRange->SetStartBefore(*aContent, IgnoreErrors());
2308 newRange->SetEndBefore(*aContent, IgnoreErrors());
2310 domSelection->AddRange(*newRange, IgnoreErrors());
2311 domSelection->CollapseToStart(IgnoreErrors());
2317 nsresult nsFocusManager::SetCaretVisible(PresShell* aPresShell, bool aVisible,
2318 nsIContent* aContent) {
2319 // When browsing with caret, make sure caret is visible after new focus
2320 // Return early if there is no caret. This can happen for the testcase
2321 // for bug 308025 where a window is closed in a blur handler.
2322 RefPtr<nsCaret> caret = aPresShell->GetCaret();
2323 if (!caret) return NS_OK;
2325 bool caretVisible = caret->IsVisible();
2326 if (!aVisible && !caretVisible) return NS_OK;
2328 RefPtr<nsFrameSelection> frameSelection;
2329 if (aContent) {
2330 NS_ASSERTION(aContent->GetComposedDoc() == aPresShell->GetDocument(),
2331 "Wrong document?");
2332 nsIFrame* focusFrame = aContent->GetPrimaryFrame();
2333 if (focusFrame) frameSelection = focusFrame->GetFrameSelection();
2336 RefPtr<nsFrameSelection> docFrameSelection = aPresShell->FrameSelection();
2338 if (docFrameSelection && caret &&
2339 (frameSelection == docFrameSelection || !aContent)) {
2340 Selection* domSelection =
2341 docFrameSelection->GetSelection(SelectionType::eNormal);
2342 if (domSelection) {
2343 // First, hide the caret to prevent attempting to show it in
2344 // SetCaretDOMSelection
2345 aPresShell->SetCaretEnabled(false);
2347 // Caret must blink on non-editable elements
2348 caret->SetIgnoreUserModify(true);
2349 // Tell the caret which selection to use
2350 caret->SetSelection(domSelection);
2352 // In content, we need to set the caret. The only special case is edit
2353 // fields, which have a different frame selection from the document.
2354 // They will take care of making the caret visible themselves.
2356 aPresShell->SetCaretReadOnly(false);
2357 aPresShell->SetCaretEnabled(aVisible);
2361 return NS_OK;
2364 nsresult nsFocusManager::GetSelectionLocation(Document* aDocument,
2365 PresShell* aPresShell,
2366 nsIContent** aStartContent,
2367 nsIContent** aEndContent) {
2368 *aStartContent = *aEndContent = nullptr;
2369 nsPresContext* presContext = aPresShell->GetPresContext();
2370 NS_ASSERTION(presContext, "mPresContent is null!!");
2372 RefPtr<nsFrameSelection> frameSelection = aPresShell->FrameSelection();
2374 RefPtr<Selection> domSelection;
2375 if (frameSelection) {
2376 domSelection = frameSelection->GetSelection(SelectionType::eNormal);
2379 bool isCollapsed = false;
2380 nsCOMPtr<nsIContent> startContent, endContent;
2381 uint32_t startOffset = 0;
2382 if (domSelection) {
2383 isCollapsed = domSelection->IsCollapsed();
2384 RefPtr<nsRange> domRange = domSelection->GetRangeAt(0);
2385 if (domRange) {
2386 nsCOMPtr<nsINode> startNode = domRange->GetStartContainer();
2387 nsCOMPtr<nsINode> endNode = domRange->GetEndContainer();
2388 startOffset = domRange->StartOffset();
2390 nsIContent* childContent = nullptr;
2392 startContent = do_QueryInterface(startNode);
2393 if (startContent && startContent->IsElement()) {
2394 childContent = startContent->GetChildAt_Deprecated(startOffset);
2395 if (childContent) {
2396 startContent = childContent;
2400 endContent = do_QueryInterface(endNode);
2401 if (endContent && endContent->IsElement()) {
2402 uint32_t endOffset = domRange->EndOffset();
2403 childContent = endContent->GetChildAt_Deprecated(endOffset);
2404 if (childContent) {
2405 endContent = childContent;
2409 } else {
2410 return NS_ERROR_INVALID_ARG;
2413 nsIFrame* startFrame = nullptr;
2414 if (startContent) {
2415 startFrame = startContent->GetPrimaryFrame();
2416 if (isCollapsed) {
2417 // Next check to see if our caret is at the very end of a node
2418 // If so, the caret is actually sitting in front of the next
2419 // logical frame's primary node - so for this case we need to
2420 // change caretContent to that node.
2422 if (startContent->NodeType() == nsINode::TEXT_NODE) {
2423 nsAutoString nodeValue;
2424 startContent->GetAsText()->AppendTextTo(nodeValue);
2426 bool isFormControl =
2427 startContent->IsNodeOfType(nsINode::eHTML_FORM_CONTROL);
2429 if (nodeValue.Length() == startOffset && !isFormControl &&
2430 startContent != aDocument->GetRootElement()) {
2431 // Yes, indeed we were at the end of the last node
2432 nsCOMPtr<nsIFrameEnumerator> frameTraversal;
2433 nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
2434 presContext, startFrame, eLeaf,
2435 false, // aVisual
2436 false, // aLockInScrollView
2437 true, // aFollowOOFs
2438 false // aSkipPopupChecks
2440 NS_ENSURE_SUCCESS(rv, rv);
2442 nsIFrame* newCaretFrame = nullptr;
2443 nsCOMPtr<nsIContent> newCaretContent = startContent;
2444 bool endOfSelectionInStartNode(startContent == endContent);
2445 do {
2446 // Continue getting the next frame until the primary content for the
2447 // frame we are on changes - we don't want to be stuck in the same
2448 // place
2449 frameTraversal->Next();
2450 newCaretFrame =
2451 static_cast<nsIFrame*>(frameTraversal->CurrentItem());
2452 if (nullptr == newCaretFrame) break;
2453 newCaretContent = newCaretFrame->GetContent();
2454 } while (!newCaretContent || newCaretContent == startContent);
2456 if (newCaretFrame && newCaretContent) {
2457 // If the caret is exactly at the same position of the new frame,
2458 // then we can use the newCaretFrame and newCaretContent for our
2459 // position
2460 nsRect caretRect;
2461 nsIFrame* frame = nsCaret::GetGeometry(domSelection, &caretRect);
2462 if (frame) {
2463 nsPoint caretWidgetOffset;
2464 nsIWidget* widget = frame->GetNearestWidget(caretWidgetOffset);
2465 caretRect.MoveBy(caretWidgetOffset);
2466 nsPoint newCaretOffset;
2467 nsIWidget* newCaretWidget =
2468 newCaretFrame->GetNearestWidget(newCaretOffset);
2469 if (widget == newCaretWidget && caretRect.y == newCaretOffset.y &&
2470 caretRect.x == newCaretOffset.x) {
2471 // The caret is at the start of the new element.
2472 startFrame = newCaretFrame;
2473 startContent = newCaretContent;
2474 if (endOfSelectionInStartNode) {
2475 endContent = newCaretContent; // Ensure end of selection is
2476 // not before start
2486 *aStartContent = startContent;
2487 *aEndContent = endContent;
2488 NS_IF_ADDREF(*aStartContent);
2489 NS_IF_ADDREF(*aEndContent);
2491 return NS_OK;
2494 nsresult nsFocusManager::DetermineElementToMoveFocus(
2495 nsPIDOMWindowOuter* aWindow, nsIContent* aStartContent, int32_t aType,
2496 bool aNoParentTraversal, nsIContent** aNextContent) {
2497 *aNextContent = nullptr;
2499 // True if we are navigating by document (F6/Shift+F6) or false if we are
2500 // navigating by element (Tab/Shift+Tab).
2501 bool forDocumentNavigation = false;
2503 // This is used for document navigation only. It will be set to true if we
2504 // start navigating from a starting point. If this starting point is near the
2505 // end of the document (for example, an element on a statusbar), and there
2506 // are no child documents or panels before the end of the document, then we
2507 // will need to ensure that we don't consider the root chrome window when we
2508 // loop around and instead find the next child document/panel, as focus is
2509 // already in that window. This flag will be cleared once we navigate into
2510 // another document.
2511 bool mayFocusRoot = (aStartContent != nullptr);
2513 nsCOMPtr<nsIContent> startContent = aStartContent;
2514 if (!startContent && aType != MOVEFOCUS_CARET) {
2515 if (aType == MOVEFOCUS_FORWARDDOC || aType == MOVEFOCUS_BACKWARDDOC) {
2516 // When moving between documents, make sure to get the right
2517 // starting content in a descendant.
2518 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
2519 startContent = GetFocusedDescendant(aWindow, eIncludeAllDescendants,
2520 getter_AddRefs(focusedWindow));
2521 } else if (aType != MOVEFOCUS_LASTDOC) {
2522 // Otherwise, start at the focused node. If MOVEFOCUS_LASTDOC is used,
2523 // then we are document-navigating backwards from chrome to the content
2524 // process, and we don't want to use this so that we start from the end
2525 // of the document.
2526 startContent = aWindow->GetFocusedElement();
2530 nsCOMPtr<Document> doc;
2531 if (startContent)
2532 doc = startContent->GetComposedDoc();
2533 else
2534 doc = aWindow->GetExtantDoc();
2535 if (!doc) return NS_OK;
2537 LookAndFeel::GetInt(LookAndFeel::eIntID_TabFocusModel,
2538 &nsIContent::sTabFocusModel);
2540 // These types are for document navigation using F6.
2541 if (aType == MOVEFOCUS_FORWARDDOC || aType == MOVEFOCUS_BACKWARDDOC ||
2542 aType == MOVEFOCUS_FIRSTDOC || aType == MOVEFOCUS_LASTDOC) {
2543 forDocumentNavigation = true;
2546 // If moving to the root or first document, find the root element and return.
2547 if (aType == MOVEFOCUS_ROOT || aType == MOVEFOCUS_FIRSTDOC) {
2548 NS_IF_ADDREF(*aNextContent = GetRootForFocus(aWindow, doc, false, false));
2549 if (!*aNextContent && aType == MOVEFOCUS_FIRSTDOC) {
2550 // When looking for the first document, if the root wasn't focusable,
2551 // find the next focusable document.
2552 aType = MOVEFOCUS_FORWARDDOC;
2553 } else {
2554 return NS_OK;
2558 Element* rootContent = doc->GetRootElement();
2559 NS_ENSURE_TRUE(rootContent, NS_OK);
2561 PresShell* presShell = doc->GetPresShell();
2562 NS_ENSURE_TRUE(presShell, NS_OK);
2564 if (aType == MOVEFOCUS_FIRST) {
2565 if (!aStartContent) startContent = rootContent;
2566 return GetNextTabbableContent(presShell, startContent, nullptr,
2567 startContent, true, 1, false, false,
2568 aNextContent);
2570 if (aType == MOVEFOCUS_LAST) {
2571 if (!aStartContent) startContent = rootContent;
2572 return GetNextTabbableContent(presShell, startContent, nullptr,
2573 startContent, false, 0, false, false,
2574 aNextContent);
2577 bool forward = (aType == MOVEFOCUS_FORWARD || aType == MOVEFOCUS_FORWARDDOC ||
2578 aType == MOVEFOCUS_CARET);
2579 bool doNavigation = true;
2580 bool ignoreTabIndex = false;
2581 // when a popup is open, we want to ensure that tab navigation occurs only
2582 // within the most recently opened panel. If a popup is open, its frame will
2583 // be stored in popupFrame.
2584 nsIFrame* popupFrame = nullptr;
2586 int32_t tabIndex = forward ? 1 : 0;
2587 if (startContent) {
2588 nsIFrame* frame = startContent->GetPrimaryFrame();
2589 if (startContent->IsHTMLElement(nsGkAtoms::area))
2590 startContent->IsFocusable(&tabIndex);
2591 else if (frame)
2592 frame->IsFocusable(&tabIndex, 0);
2593 else
2594 startContent->IsFocusable(&tabIndex);
2596 // if the current element isn't tabbable, ignore the tabindex and just
2597 // look for the next element. The root content won't have a tabindex
2598 // so just treat this as the beginning of the tab order.
2599 if (tabIndex < 0) {
2600 tabIndex = 1;
2601 if (startContent != rootContent) ignoreTabIndex = true;
2604 // check if the focus is currently inside a popup. Elements such as the
2605 // autocomplete widget use the noautofocus attribute to allow the focus to
2606 // remain outside the popup when it is opened.
2607 if (frame) {
2608 popupFrame = nsLayoutUtils::GetClosestFrameOfType(
2609 frame, LayoutFrameType::MenuPopup);
2612 if (popupFrame && !forDocumentNavigation) {
2613 // Don't navigate outside of a popup, so pretend that the
2614 // root content is the popup itself
2615 rootContent = popupFrame->GetContent()->AsElement();
2616 NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
2617 } else if (!forward) {
2618 // If focus moves backward and when current focused node is root
2619 // content or <body> element which is editable by contenteditable
2620 // attribute, focus should move to its parent document.
2621 if (startContent == rootContent) {
2622 doNavigation = false;
2623 } else {
2624 Document* doc = startContent->GetComposedDoc();
2625 if (startContent ==
2626 nsLayoutUtils::GetEditableRootContentByContentEditable(doc)) {
2627 doNavigation = false;
2631 } else {
2632 #ifdef MOZ_XUL
2633 if (aType != MOVEFOCUS_CARET) {
2634 // if there is no focus, yet a panel is open, focus the first item in
2635 // the panel
2636 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2637 if (pm) popupFrame = pm->GetTopPopup(ePopupTypePanel);
2639 #endif
2640 if (popupFrame) {
2641 // When there is a popup open, and no starting content, start the search
2642 // at the topmost popup.
2643 startContent = popupFrame->GetContent();
2644 NS_ASSERTION(startContent, "Popup frame doesn't have a content node");
2645 // Unless we are searching for documents, set the root content to the
2646 // popup as well, so that we don't tab-navigate outside the popup.
2647 // When navigating by documents, we start at the popup but can navigate
2648 // outside of it to look for other panels and documents.
2649 if (!forDocumentNavigation) {
2650 rootContent = startContent->AsElement();
2653 doc = startContent ? startContent->GetComposedDoc() : nullptr;
2654 } else {
2655 // Otherwise, for content shells, start from the location of the caret.
2656 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
2657 if (docShell && docShell->ItemType() != nsIDocShellTreeItem::typeChrome) {
2658 nsCOMPtr<nsIContent> endSelectionContent;
2659 GetSelectionLocation(doc, presShell, getter_AddRefs(startContent),
2660 getter_AddRefs(endSelectionContent));
2661 // If the selection is on the rootContent, then there is no selection
2662 if (startContent == rootContent) {
2663 startContent = nullptr;
2666 if (aType == MOVEFOCUS_CARET) {
2667 // GetFocusInSelection finds a focusable link near the caret.
2668 // If there is no start content though, don't do this to avoid
2669 // focusing something unexpected.
2670 if (startContent) {
2671 GetFocusInSelection(aWindow, startContent, endSelectionContent,
2672 aNextContent);
2674 return NS_OK;
2677 if (startContent) {
2678 // when starting from a selection, we always want to find the next or
2679 // previous element in the document. So the tabindex on elements
2680 // should be ignored.
2681 ignoreTabIndex = true;
2685 if (!startContent) {
2686 // otherwise, just use the root content as the starting point
2687 startContent = rootContent;
2688 NS_ENSURE_TRUE(startContent, NS_OK);
2693 // Check if the starting content is the same as the content assigned to the
2694 // retargetdocumentfocus attribute. Is so, we don't want to start searching
2695 // from there but instead from the beginning of the document. Otherwise, the
2696 // content that appears before the retargetdocumentfocus element will never
2697 // get checked as it will be skipped when the focus is retargetted to it.
2698 if (forDocumentNavigation && nsContentUtils::IsChromeDoc(doc)) {
2699 nsAutoString retarget;
2701 if (rootContent->GetAttr(kNameSpaceID_None,
2702 nsGkAtoms::retargetdocumentfocus, retarget)) {
2703 nsIContent* retargetElement = doc->GetElementById(retarget);
2704 // The common case here is the urlbar where focus is on the anonymous
2705 // input inside the textbox, but the retargetdocumentfocus attribute
2706 // refers to the textbox. The Contains check will return false and the
2707 // ContentIsDescendantOf check will return true in this case.
2708 if (retargetElement && (retargetElement == startContent ||
2709 (!retargetElement->Contains(startContent) &&
2710 nsContentUtils::ContentIsDescendantOf(
2711 startContent, retargetElement)))) {
2712 startContent = rootContent;
2717 NS_ASSERTION(startContent, "starting content not set");
2719 // keep a reference to the starting content. If we find that again, it means
2720 // we've iterated around completely and we don't want to adjust the focus.
2721 // The skipOriginalContentCheck will be set to true only for the first time
2722 // GetNextTabbableContent is called. This ensures that we don't break out
2723 // when nothing is focused to start with. Specifically,
2724 // GetNextTabbableContent first checks the root content -- which happens to
2725 // be the same as the start content -- when nothing is focused and tabbing
2726 // forward. Without skipOriginalContentCheck set to true, we'd end up
2727 // returning right away and focusing nothing. Luckily, GetNextTabbableContent
2728 // will never wrap around on its own, and can only return the original
2729 // content when it is called a second time or later.
2730 bool skipOriginalContentCheck = true;
2731 nsIContent* originalStartContent = startContent;
2733 LOGCONTENTNAVIGATION("Focus Navigation Start Content %s", startContent.get());
2734 LOGFOCUSNAVIGATION((" Forward: %d Tabindex: %d Ignore: %d DocNav: %d",
2735 forward, tabIndex, ignoreTabIndex,
2736 forDocumentNavigation));
2738 while (doc) {
2739 if (doNavigation) {
2740 nsCOMPtr<nsIContent> nextFocus;
2741 nsresult rv = GetNextTabbableContent(
2742 presShell, rootContent,
2743 skipOriginalContentCheck ? nullptr : originalStartContent,
2744 startContent, forward, tabIndex, ignoreTabIndex,
2745 forDocumentNavigation, getter_AddRefs(nextFocus));
2746 NS_ENSURE_SUCCESS(rv, rv);
2747 if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
2748 // Navigation was redirected to a child process, so just return.
2749 return NS_OK;
2752 // found a content node to focus.
2753 if (nextFocus) {
2754 LOGCONTENTNAVIGATION("Next Content: %s", nextFocus.get());
2756 // as long as the found node was not the same as the starting node,
2757 // set it as the return value. For document navigation, we can return
2758 // the same element in case there is only one content node that could
2759 // be returned, for example, in a child process document.
2760 if (nextFocus != originalStartContent || forDocumentNavigation) {
2761 nextFocus.forget(aNextContent);
2763 return NS_OK;
2766 if (popupFrame && !forDocumentNavigation) {
2767 // in a popup, so start again from the beginning of the popup. However,
2768 // if we already started at the beginning, then there isn't anything to
2769 // focus, so just return
2770 if (startContent != rootContent) {
2771 startContent = rootContent;
2772 tabIndex = forward ? 1 : 0;
2773 continue;
2775 return NS_OK;
2779 doNavigation = true;
2780 skipOriginalContentCheck = forDocumentNavigation;
2781 ignoreTabIndex = false;
2783 if (aNoParentTraversal) {
2784 if (startContent == rootContent) return NS_OK;
2786 startContent = rootContent;
2787 tabIndex = forward ? 1 : 0;
2788 continue;
2791 // Reached the beginning or end of the document. Next, navigate up to the
2792 // parent document and try again.
2793 nsCOMPtr<nsPIDOMWindowOuter> piWindow = doc->GetWindow();
2794 NS_ENSURE_TRUE(piWindow, NS_ERROR_FAILURE);
2796 nsCOMPtr<nsIDocShell> docShell = piWindow->GetDocShell();
2797 NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
2799 // Get the frame element this window is inside and, from that, get the
2800 // parent document and presshell. If there is no enclosing frame element,
2801 // then this is a top-level, embedded or remote window.
2802 startContent = piWindow->GetFrameElementInternal();
2803 if (startContent) {
2804 doc = startContent->GetComposedDoc();
2805 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
2807 rootContent = doc->GetRootElement();
2808 presShell = doc->GetPresShell();
2810 // We can focus the root element now that we have moved to another
2811 // document.
2812 mayFocusRoot = true;
2814 nsIFrame* frame = startContent->GetPrimaryFrame();
2815 if (!frame) {
2816 return NS_OK;
2819 frame->IsFocusable(&tabIndex, 0);
2820 if (tabIndex < 0) {
2821 tabIndex = 1;
2822 ignoreTabIndex = true;
2825 // if the frame is inside a popup, make sure to scan only within the
2826 // popup. This handles the situation of tabbing amongst elements
2827 // inside an iframe which is itself inside a popup. Otherwise,
2828 // navigation would move outside the popup when tabbing outside the
2829 // iframe.
2830 if (!forDocumentNavigation) {
2831 popupFrame = nsLayoutUtils::GetClosestFrameOfType(
2832 frame, LayoutFrameType::MenuPopup);
2833 if (popupFrame) {
2834 rootContent = popupFrame->GetContent()->AsElement();
2835 NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
2838 } else {
2839 // There is no parent, so call the tree owner. This will tell the
2840 // embedder or parent process that it should take the focus.
2841 bool tookFocus;
2842 docShell->TabToTreeOwner(forward, forDocumentNavigation, &tookFocus);
2843 // If the tree owner took the focus, blur the current element.
2844 if (tookFocus) {
2845 nsCOMPtr<nsPIDOMWindowOuter> window = docShell->GetWindow();
2846 if (window->GetFocusedElement() == mFocusedElement)
2847 Blur(mFocusedWindow, nullptr, true, true);
2848 else
2849 window->SetFocusedElement(nullptr);
2850 return NS_OK;
2853 // If we have reached the end of the top-level document, focus the
2854 // first element in the top-level document. This should always happen
2855 // when navigating by document forwards but when navigating backwards,
2856 // only do this if we started in another document or within a popup frame.
2857 // If the focus started in this window outside a popup however, we should
2858 // continue by looping around to the end again.
2859 if (forDocumentNavigation && (forward || mayFocusRoot || popupFrame)) {
2860 // HTML content documents can have their root element focused (a focus
2861 // ring appears around the entire content area frame). This root
2862 // appears in the tab order before all of the elements in the document.
2863 // Chrome documents however cannot be focused directly, so instead we
2864 // focus the first focusable element within the window.
2865 // For example, the urlbar.
2866 Element* root = GetRootForFocus(piWindow, doc, true, true);
2867 return FocusFirst(root, aNextContent);
2870 // Once we have hit the top-level and have iterated to the end again, we
2871 // just want to break out next time we hit this spot to prevent infinite
2872 // iteration.
2873 mayFocusRoot = true;
2875 // reset the tab index and start again from the beginning or end
2876 startContent = rootContent;
2877 tabIndex = forward ? 1 : 0;
2880 // wrapped all the way around and didn't find anything to move the focus
2881 // to, so just break out
2882 if (startContent == originalStartContent) break;
2885 return NS_OK;
2888 uint32_t nsFocusManager::FocusOptionsToFocusManagerFlags(
2889 const mozilla::dom::FocusOptions& aOptions) {
2890 return aOptions.mPreventScroll ? nsIFocusManager::FLAG_NOSCROLL : 0;
2893 static bool IsHostOrSlot(const nsIContent* aContent) {
2894 return aContent && (aContent->GetShadowRoot() ||
2895 aContent->IsHTMLElement(nsGkAtoms::slot));
2898 // Helper class to iterate contents in scope by traversing flattened tree
2899 // in tree order
2900 class MOZ_STACK_CLASS ScopedContentTraversal {
2901 public:
2902 ScopedContentTraversal(nsIContent* aStartContent, nsIContent* aOwner)
2903 : mCurrent(aStartContent), mOwner(aOwner) {
2904 MOZ_ASSERT(aStartContent);
2907 void Next();
2908 void Prev();
2910 void Reset() { SetCurrent(mOwner); }
2912 nsIContent* GetCurrent() const { return mCurrent; }
2914 private:
2915 void SetCurrent(nsIContent* aContent) { mCurrent = aContent; }
2917 nsIContent* mCurrent;
2918 nsIContent* mOwner;
2921 void ScopedContentTraversal::Next() {
2922 MOZ_ASSERT(mCurrent);
2924 // Get mCurrent's first child if it's in the same scope.
2925 if (!IsHostOrSlot(mCurrent) || mCurrent == mOwner) {
2926 StyleChildrenIterator iter(mCurrent);
2927 nsIContent* child = iter.GetNextChild();
2928 if (child) {
2929 SetCurrent(child);
2930 return;
2934 // If mOwner has no children, END traversal
2935 if (mCurrent == mOwner) {
2936 SetCurrent(nullptr);
2937 return;
2940 nsIContent* current = mCurrent;
2941 while (1) {
2942 // Create parent's iterator and move to current
2943 nsIContent* parent = current->GetFlattenedTreeParent();
2944 StyleChildrenIterator parentIter(parent);
2945 parentIter.Seek(current);
2947 // Get next sibling of current
2948 if (nsIContent* next = parentIter.GetNextChild()) {
2949 SetCurrent(next);
2950 return;
2953 // If no next sibling and parent is mOwner, END traversal
2954 if (parent == mOwner) {
2955 SetCurrent(nullptr);
2956 return;
2959 current = parent;
2963 void ScopedContentTraversal::Prev() {
2964 MOZ_ASSERT(mCurrent);
2966 nsIContent* parent;
2967 nsIContent* last;
2968 if (mCurrent == mOwner) {
2969 // Get last child of mOwner
2970 StyleChildrenIterator ownerIter(mOwner, false /* aStartAtBeginning */);
2971 last = ownerIter.GetPreviousChild();
2973 parent = last;
2974 } else {
2975 // Create parent's iterator and move to mCurrent
2976 parent = mCurrent->GetFlattenedTreeParent();
2977 StyleChildrenIterator parentIter(parent);
2978 parentIter.Seek(mCurrent);
2980 // Get previous sibling
2981 last = parentIter.GetPreviousChild();
2984 while (last) {
2985 parent = last;
2986 if (IsHostOrSlot(parent)) {
2987 // Skip contents in other scopes
2988 break;
2991 // Find last child
2992 StyleChildrenIterator iter(parent, false /* aStartAtBeginning */);
2993 last = iter.GetPreviousChild();
2996 // If parent is mOwner and no previous sibling remains, END traversal
2997 SetCurrent(parent == mOwner ? nullptr : parent);
3001 * Returns scope owner of aContent.
3002 * A scope owner is either a document root, shadow host, or slot.
3004 static nsIContent* FindOwner(nsIContent* aContent) {
3005 nsIContent* currentContent = aContent;
3006 while (currentContent) {
3007 nsIContent* parent = currentContent->GetFlattenedTreeParent();
3009 // Shadow host / Slot
3010 if (IsHostOrSlot(parent)) {
3011 return parent;
3014 currentContent = parent;
3017 return nullptr;
3021 * Host and Slot elements need to be handled as if they had tabindex 0 even
3022 * when they don't have the attribute. This is a helper method to get the
3023 * right value for focus navigation. If aIsFocusable is passed, it is set to
3024 * true if the element itself is focusable.
3026 static int32_t HostOrSlotTabIndexValue(const nsIContent* aContent,
3027 bool* aIsFocusable = nullptr) {
3028 MOZ_ASSERT(IsHostOrSlot(aContent));
3030 if (aIsFocusable) {
3031 *aIsFocusable = false;
3032 nsIFrame* frame = aContent->GetPrimaryFrame();
3033 if (frame) {
3034 int32_t tabIndex;
3035 frame->IsFocusable(&tabIndex, 0);
3036 *aIsFocusable = tabIndex >= 0;
3040 const nsAttrValue* attrVal =
3041 aContent->AsElement()->GetParsedAttr(nsGkAtoms::tabindex);
3042 if (!attrVal) {
3043 return 0;
3046 if (attrVal->Type() == nsAttrValue::eInteger) {
3047 return attrVal->GetIntegerValue();
3050 return -1;
3053 nsIContent* nsFocusManager::GetNextTabbableContentInScope(
3054 nsIContent* aOwner, nsIContent* aStartContent,
3055 nsIContent* aOriginalStartContent, bool aForward, int32_t aCurrentTabIndex,
3056 bool aIgnoreTabIndex, bool aForDocumentNavigation, bool aSkipOwner) {
3057 MOZ_ASSERT(IsHostOrSlot(aOwner), "Scope owner should be host or slot");
3059 if (!aSkipOwner && (aForward && aOwner == aStartContent)) {
3060 int32_t tabIndex = -1;
3061 nsIFrame* frame = aOwner->GetPrimaryFrame();
3062 if (frame && frame->IsFocusable(&tabIndex, false) && tabIndex >= 0) {
3063 return aOwner;
3068 // Iterate contents in scope
3070 ScopedContentTraversal contentTraversal(aStartContent, aOwner);
3071 nsCOMPtr<nsIContent> iterContent;
3072 nsIContent* firstNonChromeOnly =
3073 aStartContent->IsInNativeAnonymousSubtree()
3074 ? aStartContent->FindFirstNonChromeOnlyAccessContent()
3075 : nullptr;
3076 while (1) {
3077 // Iterate tab index to find corresponding contents in scope
3079 while (1) {
3080 // Iterate remaining contents in scope to find next content to focus
3082 // Get next content
3083 aForward ? contentTraversal.Next() : contentTraversal.Prev();
3084 iterContent = contentTraversal.GetCurrent();
3086 if (firstNonChromeOnly && firstNonChromeOnly == iterContent) {
3087 // We just broke out from the native anonymous content, so move
3088 // to the previous/next node of the native anonymous owner.
3089 if (aForward) {
3090 contentTraversal.Next();
3091 } else {
3092 contentTraversal.Prev();
3094 iterContent = contentTraversal.GetCurrent();
3096 if (!iterContent) {
3097 // Reach the end
3098 break;
3101 int32_t tabIndex = 0;
3102 if (iterContent->IsInNativeAnonymousSubtree() &&
3103 iterContent->GetPrimaryFrame()) {
3104 iterContent->GetPrimaryFrame()->IsFocusable(&tabIndex);
3105 } else if (IsHostOrSlot(iterContent)) {
3106 tabIndex = HostOrSlotTabIndexValue(iterContent);
3107 } else {
3108 nsIFrame* frame = iterContent->GetPrimaryFrame();
3109 if (!frame) {
3110 continue;
3112 frame->IsFocusable(&tabIndex, 0);
3114 if (tabIndex < 0 || !(aIgnoreTabIndex || tabIndex == aCurrentTabIndex)) {
3115 continue;
3118 if (!IsHostOrSlot(iterContent)) {
3119 nsCOMPtr<nsIContent> elementInFrame;
3120 bool checkSubDocument = true;
3121 if (aForDocumentNavigation &&
3122 TryDocumentNavigation(iterContent, &checkSubDocument,
3123 getter_AddRefs(elementInFrame))) {
3124 return elementInFrame;
3126 if (!checkSubDocument) {
3127 continue;
3130 if (TryToMoveFocusToSubDocument(iterContent, aOriginalStartContent,
3131 aForward, aForDocumentNavigation,
3132 getter_AddRefs(elementInFrame))) {
3133 return elementInFrame;
3136 // Found content to focus
3137 return iterContent;
3140 // Search in scope owned by iterContent
3141 nsIContent* contentToFocus = GetNextTabbableContentInScope(
3142 iterContent, iterContent, aOriginalStartContent, aForward,
3143 aForward ? 1 : 0, aIgnoreTabIndex, aForDocumentNavigation,
3144 false /* aSkipOwner */);
3145 if (contentToFocus) {
3146 return contentToFocus;
3150 // If already at lowest priority tab (0), end search completely.
3151 // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
3152 if (aCurrentTabIndex == (aForward ? 0 : 1)) {
3153 break;
3156 // We've been just trying to find some focusable element, and haven't, so
3157 // bail out.
3158 if (aIgnoreTabIndex) {
3159 break;
3162 // Continue looking for next highest priority tabindex
3163 aCurrentTabIndex = GetNextTabIndex(aOwner, aCurrentTabIndex, aForward);
3164 contentTraversal.Reset();
3167 // Return scope owner at last for backward navigation if its tabindex
3168 // is non-negative
3169 if (!aSkipOwner && !aForward) {
3170 int32_t tabIndex = -1;
3171 nsIFrame* frame = aOwner->GetPrimaryFrame();
3172 if (frame && frame->IsFocusable(&tabIndex, false) && tabIndex >= 0) {
3173 return aOwner;
3177 return nullptr;
3180 nsIContent* nsFocusManager::GetNextTabbableContentInAncestorScopes(
3181 nsIContent* aStartOwner, nsIContent** aStartContent,
3182 nsIContent* aOriginalStartContent, bool aForward, int32_t* aCurrentTabIndex,
3183 bool aIgnoreTabIndex, bool aForDocumentNavigation) {
3184 MOZ_ASSERT(aStartOwner == FindOwner(*aStartContent),
3185 "aStartOWner should be the scope owner of aStartContent");
3186 MOZ_ASSERT(IsHostOrSlot(aStartOwner), "scope owner should be host or slot");
3188 nsIContent* owner = aStartOwner;
3189 nsIContent* startContent = *aStartContent;
3190 while (IsHostOrSlot(owner)) {
3191 int32_t tabIndex = 0;
3192 if (IsHostOrSlot(startContent)) {
3193 tabIndex = HostOrSlotTabIndexValue(startContent);
3194 } else if (nsIFrame* frame = startContent->GetPrimaryFrame()) {
3195 frame->IsFocusable(&tabIndex);
3196 } else {
3197 startContent->IsFocusable(&tabIndex);
3199 nsIContent* contentToFocus = GetNextTabbableContentInScope(
3200 owner, startContent, aOriginalStartContent, aForward, tabIndex,
3201 aIgnoreTabIndex, aForDocumentNavigation, false /* aSkipOwner */);
3202 if (contentToFocus) {
3203 return contentToFocus;
3206 startContent = owner;
3207 owner = FindOwner(startContent);
3210 // If not found in shadow DOM, search from the top level shadow host in light
3211 // DOM
3212 *aStartContent = startContent;
3213 *aCurrentTabIndex = HostOrSlotTabIndexValue(startContent);
3215 return nullptr;
3218 static nsIContent* GetTopLevelScopeOwner(nsIContent* aContent) {
3219 nsIContent* topLevelScopeOwner = nullptr;
3220 while (aContent) {
3221 if (HTMLSlotElement* slot = aContent->GetAssignedSlot()) {
3222 aContent = slot;
3223 } else if (ShadowRoot* shadowRoot = aContent->GetContainingShadow()) {
3224 aContent = shadowRoot->Host();
3225 topLevelScopeOwner = aContent;
3226 } else {
3227 if (HTMLSlotElement::FromNode(aContent)) {
3228 topLevelScopeOwner = aContent;
3230 aContent = aContent->GetParent();
3234 return topLevelScopeOwner;
3237 nsresult nsFocusManager::GetNextTabbableContent(
3238 PresShell* aPresShell, nsIContent* aRootContent,
3239 nsIContent* aOriginalStartContent, nsIContent* aStartContent, bool aForward,
3240 int32_t aCurrentTabIndex, bool aIgnoreTabIndex, bool aForDocumentNavigation,
3241 nsIContent** aResultContent) {
3242 *aResultContent = nullptr;
3244 nsCOMPtr<nsIContent> startContent = aStartContent;
3245 if (!startContent) return NS_OK;
3247 nsIContent* currentTopLevelScopeOwner = GetTopLevelScopeOwner(aStartContent);
3249 LOGCONTENTNAVIGATION("GetNextTabbable: %s", aStartContent);
3250 LOGFOCUSNAVIGATION((" tabindex: %d", aCurrentTabIndex));
3252 // If aStartContent is a shadow host or slot in forward navigation,
3253 // search in scope owned by aStartContent
3254 if (aForward && IsHostOrSlot(aStartContent)) {
3255 nsIContent* contentToFocus = GetNextTabbableContentInScope(
3256 aStartContent, aStartContent, aOriginalStartContent, aForward,
3257 aForward ? 1 : 0, aIgnoreTabIndex, aForDocumentNavigation,
3258 true /* aSkipOwner */);
3259 if (contentToFocus) {
3260 NS_ADDREF(*aResultContent = contentToFocus);
3261 return NS_OK;
3265 // If aStartContent is in a scope owned by Shadow DOM search from scope
3266 // including aStartContent
3267 if (nsIContent* owner = FindOwner(aStartContent)) {
3268 nsIContent* contentToFocus = GetNextTabbableContentInAncestorScopes(
3269 owner, &aStartContent, aOriginalStartContent, aForward,
3270 &aCurrentTabIndex, aIgnoreTabIndex, aForDocumentNavigation);
3271 if (contentToFocus) {
3272 NS_ADDREF(*aResultContent = contentToFocus);
3273 return NS_OK;
3277 // If we reach here, it means no next tabbable content in shadow DOM.
3278 // We need to continue searching in light DOM, starting at the top level
3279 // shadow host in light DOM (updated aStartContent) and its tabindex
3280 // (updated aCurrentTabIndex).
3281 MOZ_ASSERT(!FindOwner(aStartContent),
3282 "aStartContent should not be owned by Shadow DOM at this point");
3284 nsPresContext* presContext = aPresShell->GetPresContext();
3286 bool getNextFrame = true;
3287 nsCOMPtr<nsIContent> iterStartContent = aStartContent;
3288 // Iterate tab index to find corresponding contents
3289 while (1) {
3290 nsIFrame* frame = iterStartContent->GetPrimaryFrame();
3291 // if there is no frame, look for another content node that has a frame
3292 while (!frame) {
3293 // if the root content doesn't have a frame, just return
3294 if (iterStartContent == aRootContent) {
3295 return NS_OK;
3298 // look for the next or previous content node in tree order
3299 iterStartContent = aForward ? iterStartContent->GetNextNode()
3300 : iterStartContent->GetPreviousContent();
3301 if (!iterStartContent) {
3302 break;
3305 frame = iterStartContent->GetPrimaryFrame();
3306 // Host without frame, enter its scope.
3307 if (!frame && iterStartContent->GetShadowRoot()) {
3308 int32_t tabIndex = HostOrSlotTabIndexValue(iterStartContent);
3309 if (tabIndex >= 0 &&
3310 (aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) {
3311 nsIContent* contentToFocus = GetNextTabbableContentInScope(
3312 iterStartContent, iterStartContent, aOriginalStartContent,
3313 aForward, aForward ? 1 : 0, aIgnoreTabIndex,
3314 aForDocumentNavigation, true /* aSkipOwner */);
3315 if (contentToFocus) {
3316 NS_ADDREF(*aResultContent = contentToFocus);
3317 return NS_OK;
3321 // we've already skipped over the initial focused content, so we
3322 // don't want to traverse frames.
3323 getNextFrame = false;
3326 nsCOMPtr<nsIFrameEnumerator> frameTraversal;
3327 if (frame) {
3328 // For tab navigation, pass false for aSkipPopupChecks so that we don't
3329 // iterate into or out of a popup. For document naviation pass true to
3330 // ignore these boundaries.
3331 nsresult rv = NS_NewFrameTraversal(
3332 getter_AddRefs(frameTraversal), presContext, frame, ePreOrder,
3333 false, // aVisual
3334 false, // aLockInScrollView
3335 true, // aFollowOOFs
3336 aForDocumentNavigation // aSkipPopupChecks
3338 NS_ENSURE_SUCCESS(rv, rv);
3340 if (iterStartContent == aRootContent) {
3341 if (!aForward) {
3342 frameTraversal->Last();
3343 } else if (aRootContent->IsFocusable()) {
3344 frameTraversal->Next();
3346 frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
3347 } else if (getNextFrame &&
3348 (!iterStartContent ||
3349 !iterStartContent->IsHTMLElement(nsGkAtoms::area))) {
3350 // Need to do special check in case we're in an imagemap which has
3351 // multiple content nodes per frame, so don't skip over the starting
3352 // frame.
3353 if (aForward) {
3354 frameTraversal->Next();
3355 } else {
3356 frameTraversal->Prev();
3359 frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
3363 // Walk frames to find something tabbable matching aCurrentTabIndex
3364 while (frame) {
3365 // Try to find the topmost scope owner, since we want to skip the node
3366 // that is not owned by document in frame traversal.
3367 nsIContent* currentContent = frame->GetContent();
3368 nsIContent* oldTopLevelScopeOwner = currentTopLevelScopeOwner;
3369 if (oldTopLevelScopeOwner != currentContent) {
3370 currentTopLevelScopeOwner = GetTopLevelScopeOwner(currentContent);
3371 } else {
3372 currentTopLevelScopeOwner = currentContent;
3374 if (currentTopLevelScopeOwner) {
3375 if (currentTopLevelScopeOwner == oldTopLevelScopeOwner) {
3376 // We're within non-document scope, continue.
3377 do {
3378 if (aForward) {
3379 frameTraversal->Next();
3380 } else {
3381 frameTraversal->Prev();
3383 frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
3384 // For the usage of GetPrevContinuation, see the comment
3385 // at the end of while (frame) loop.
3386 } while (frame && frame->GetPrevContinuation());
3387 continue;
3389 currentContent = currentTopLevelScopeOwner;
3392 // For document navigation, check if this element is an open panel. Since
3393 // panels aren't focusable (tabIndex would be -1), we'll just assume that
3394 // for document navigation, the tabIndex is 0.
3395 if (aForDocumentNavigation && currentContent && (aCurrentTabIndex == 0) &&
3396 currentContent->IsXULElement(nsGkAtoms::panel)) {
3397 nsMenuPopupFrame* popupFrame = do_QueryFrame(frame);
3398 // Check if the panel is open. Closed panels are ignored since you can't
3399 // focus anything in them.
3400 if (popupFrame && popupFrame->IsOpen()) {
3401 // When moving backward, skip the popup we started in otherwise it
3402 // will be selected again.
3403 bool validPopup = true;
3404 if (!aForward) {
3405 nsIContent* content = aStartContent;
3406 while (content) {
3407 if (content == currentContent) {
3408 validPopup = false;
3409 break;
3412 content = content->GetParent();
3416 if (validPopup) {
3417 // Since a panel isn't focusable itself, find the first focusable
3418 // content within the popup. If there isn't any focusable content
3419 // in the popup, skip this popup and continue iterating through the
3420 // frames. We pass the panel itself (currentContent) as the starting
3421 // and root content, so that we only find content within the panel.
3422 // Note also that we pass false for aForDocumentNavigation since we
3423 // want to locate the first content, not the first document.
3424 nsresult rv = GetNextTabbableContent(
3425 aPresShell, currentContent, nullptr, currentContent, true, 1,
3426 false, false, aResultContent);
3427 if (NS_SUCCEEDED(rv) && *aResultContent) {
3428 return rv;
3434 // As of now, 2018/04/12, sequential focus navigation is still
3435 // in the obsolete Shadow DOM specification.
3436 // http://w3c.github.io/webcomponents/spec/shadow/#sequential-focus-navigation
3437 // "if ELEMENT is focusable, a shadow host, or a slot element,
3438 // append ELEMENT to NAVIGATION-ORDER."
3439 // and later in "For each element ELEMENT in NAVIGATION-ORDER: "
3440 // hosts and slots are handled before other elements.
3441 if (IsHostOrSlot(currentContent)) {
3442 bool focusableHostSlot;
3443 int32_t tabIndex =
3444 HostOrSlotTabIndexValue(currentContent, &focusableHostSlot);
3445 // Host or slot itself isn't focusable or going backwards, enter its
3446 // scope.
3447 if ((!aForward || !focusableHostSlot) && tabIndex >= 0 &&
3448 (aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) {
3449 nsIContent* contentToFocus = GetNextTabbableContentInScope(
3450 currentContent, currentContent, aOriginalStartContent, aForward,
3451 aForward ? 1 : 0, aIgnoreTabIndex, aForDocumentNavigation,
3452 true /* aSkipOwner */);
3453 if (contentToFocus) {
3454 NS_ADDREF(*aResultContent = contentToFocus);
3455 return NS_OK;
3460 // TabIndex not set defaults to 0 for form elements, anchors and other
3461 // elements that are normally focusable. Tabindex defaults to -1
3462 // for elements that are not normally focusable.
3463 // The returned computed tabindex from IsFocusable() is as follows:
3464 // clang-format off
3465 // < 0 not tabbable at all
3466 // == 0 in normal tab order (last after positive tabindexed items)
3467 // > 0 can be tabbed to in the order specified by this value
3468 // clang-format on
3469 int32_t tabIndex;
3470 frame->IsFocusable(&tabIndex, 0);
3472 LOGCONTENTNAVIGATION("Next Tabbable %s:", frame->GetContent());
3473 LOGFOCUSNAVIGATION(
3474 (" with tabindex: %d expected: %d", tabIndex, aCurrentTabIndex));
3476 if (tabIndex >= 0) {
3477 NS_ASSERTION(currentContent,
3478 "IsFocusable set a tabindex for a frame with no content");
3479 if (!aForDocumentNavigation &&
3480 currentContent->IsHTMLElement(nsGkAtoms::img) &&
3481 currentContent->AsElement()->HasAttr(kNameSpaceID_None,
3482 nsGkAtoms::usemap)) {
3483 // This is an image with a map. Image map areas are not traversed by
3484 // nsIFrameTraversal so look for the next or previous area element.
3485 nsIContent* areaContent = GetNextTabbableMapArea(
3486 aForward, aCurrentTabIndex, currentContent->AsElement(),
3487 iterStartContent);
3488 if (areaContent) {
3489 NS_ADDREF(*aResultContent = areaContent);
3490 return NS_OK;
3492 } else if (aIgnoreTabIndex || aCurrentTabIndex == tabIndex) {
3493 // break out if we've wrapped around to the start again.
3494 if (aOriginalStartContent &&
3495 currentContent == aOriginalStartContent) {
3496 NS_ADDREF(*aResultContent = currentContent);
3497 return NS_OK;
3500 // If this is a remote child browser, call NavigateDocument to have
3501 // the child process continue the navigation. Return a special error
3502 // code to have the caller return early. If the child ends up not
3503 // being focusable in some way, the child process will call back
3504 // into document navigation again by calling MoveFocus.
3505 BrowserParent* remote = BrowserParent::GetFrom(currentContent);
3506 if (remote) {
3507 remote->NavigateByKey(aForward, aForDocumentNavigation);
3508 return NS_SUCCESS_DOM_NO_OPERATION;
3511 // Same as above but for out-of-process iframes
3512 BrowserBridgeChild* bbc = BrowserBridgeChild::GetFrom(currentContent);
3513 if (bbc) {
3514 bbc->NavigateByKey(aForward, aForDocumentNavigation);
3515 return NS_SUCCESS_DOM_NO_OPERATION;
3518 // Next, for document navigation, check if this a non-remote child
3519 // document.
3520 bool checkSubDocument = true;
3521 if (aForDocumentNavigation &&
3522 TryDocumentNavigation(currentContent, &checkSubDocument,
3523 aResultContent)) {
3524 return NS_OK;
3527 if (checkSubDocument) {
3528 // found a node with a matching tab index. Check if it is a child
3529 // frame. If so, navigate into the child frame instead.
3530 if (TryToMoveFocusToSubDocument(
3531 currentContent, aOriginalStartContent, aForward,
3532 aForDocumentNavigation, aResultContent)) {
3533 MOZ_ASSERT(*aResultContent);
3534 return NS_OK;
3536 // otherwise, use this as the next content node to tab to, unless
3537 // this was the element we started on. This would happen for
3538 // instance on an element with child frames, where frame navigation
3539 // could return the original element again. In that case, just skip
3540 // it. Also, if the next content node is the root content, then
3541 // return it. This latter case would happen only if someone made a
3542 // popup focusable.
3543 // Also, when going backwards, check to ensure that the focus
3544 // wouldn't be redirected. Otherwise, for example, when an input in
3545 // a textbox is focused, the enclosing textbox would be found and
3546 // the same inner input would be returned again.
3547 else if (currentContent == aRootContent ||
3548 (currentContent != startContent &&
3549 (aForward || !GetRedirectedFocus(currentContent)))) {
3550 NS_ADDREF(*aResultContent = currentContent);
3551 return NS_OK;
3555 } else if (aOriginalStartContent &&
3556 currentContent == aOriginalStartContent) {
3557 // not focusable, so return if we have wrapped around to the original
3558 // content. This is necessary in case the original starting content was
3559 // not focusable.
3560 NS_ADDREF(*aResultContent = currentContent);
3561 return NS_OK;
3564 // Move to the next or previous frame, but ignore continuation frames
3565 // since only the first frame should be involved in focusability.
3566 // Otherwise, a loop will occur in the following example:
3567 // <span tabindex="1">...<a/><a/>...</span>
3568 // where the text wraps onto multiple lines. Tabbing from the second
3569 // link can find one of the span's continuation frames between the link
3570 // and the end of the span, and the span would end up getting focused
3571 // again.
3572 do {
3573 if (aForward)
3574 frameTraversal->Next();
3575 else
3576 frameTraversal->Prev();
3577 frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
3578 } while (frame && frame->GetPrevContinuation());
3581 // If already at lowest priority tab (0), end search completely.
3582 // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
3583 if (aCurrentTabIndex == (aForward ? 0 : 1)) {
3584 // if going backwards, the canvas should be focused once the beginning
3585 // has been reached, so get the root element.
3586 if (!aForward) {
3587 nsCOMPtr<nsPIDOMWindowOuter> window = GetCurrentWindow(aRootContent);
3588 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
3590 RefPtr<Element> docRoot = GetRootForFocus(
3591 window, aRootContent->GetComposedDoc(), false, true);
3592 FocusFirst(docRoot, aResultContent);
3594 break;
3597 // continue looking for next highest priority tabindex
3598 aCurrentTabIndex =
3599 GetNextTabIndex(aRootContent, aCurrentTabIndex, aForward);
3600 startContent = iterStartContent = aRootContent;
3603 return NS_OK;
3606 bool nsFocusManager::TryDocumentNavigation(nsIContent* aCurrentContent,
3607 bool* aCheckSubDocument,
3608 nsIContent** aResultContent) {
3609 *aCheckSubDocument = true;
3610 Element* docRoot = GetRootForChildDocument(aCurrentContent);
3611 if (docRoot) {
3612 // If GetRootForChildDocument returned something then call
3613 // FocusFirst to find the root or first element to focus within
3614 // the child document. If this is a frameset though, skip this and
3615 // fall through to normal tab navigation to iterate into
3616 // the frameset's frames and locate the first focusable frame.
3617 if (!docRoot->IsHTMLElement(nsGkAtoms::frameset)) {
3618 *aCheckSubDocument = false;
3619 Unused << FocusFirst(docRoot, aResultContent);
3620 return *aResultContent != nullptr;
3622 } else {
3623 // Set aCheckSubDocument to false, as this was neither a frame
3624 // type element or a child document that was focusable.
3625 *aCheckSubDocument = false;
3628 return false;
3631 bool nsFocusManager::TryToMoveFocusToSubDocument(
3632 nsIContent* aCurrentContent, nsIContent* aOriginalStartContent,
3633 bool aForward, bool aForDocumentNavigation, nsIContent** aResultContent) {
3634 Document* doc = aCurrentContent->GetComposedDoc();
3635 NS_ASSERTION(doc, "content not in document");
3636 Document* subdoc = doc->GetSubDocumentFor(aCurrentContent);
3637 if (subdoc && !subdoc->EventHandlingSuppressed()) {
3638 if (aForward) {
3639 // When tabbing forward into a frame, return the root
3640 // frame so that the canvas becomes focused.
3641 nsCOMPtr<nsPIDOMWindowOuter> subframe = subdoc->GetWindow();
3642 if (subframe) {
3643 *aResultContent = GetRootForFocus(subframe, subdoc, false, true);
3644 if (*aResultContent) {
3645 NS_ADDREF(*aResultContent);
3646 return true;
3650 Element* rootElement = subdoc->GetRootElement();
3651 PresShell* subPresShell = subdoc->GetPresShell();
3652 if (rootElement && subPresShell) {
3653 nsresult rv = GetNextTabbableContent(
3654 subPresShell, rootElement, aOriginalStartContent, rootElement,
3655 aForward, (aForward ? 1 : 0), false, aForDocumentNavigation,
3656 aResultContent);
3657 NS_ENSURE_SUCCESS(rv, false);
3658 if (*aResultContent) {
3659 return true;
3663 return false;
3666 nsIContent* nsFocusManager::GetNextTabbableMapArea(bool aForward,
3667 int32_t aCurrentTabIndex,
3668 Element* aImageContent,
3669 nsIContent* aStartContent) {
3670 if (aImageContent->IsInComposedDoc()) {
3671 HTMLImageElement* imgElement = HTMLImageElement::FromNode(aImageContent);
3672 // The caller should check the element type, so we can assert here.
3673 MOZ_ASSERT(imgElement);
3675 nsCOMPtr<nsIContent> mapContent = imgElement->FindImageMap();
3676 if (!mapContent) return nullptr;
3677 uint32_t count = mapContent->GetChildCount();
3678 // First see if the the start content is in this map
3680 int32_t index = mapContent->ComputeIndexOf(aStartContent);
3681 int32_t tabIndex;
3682 if (index < 0 || (aStartContent->IsFocusable(&tabIndex) &&
3683 tabIndex != aCurrentTabIndex)) {
3684 // If aStartContent is in this map we must start iterating past it.
3685 // We skip the case where aStartContent has tabindex == aStartContent
3686 // since the next tab ordered element might be before it
3687 // (or after for backwards) in the child list.
3688 index = aForward ? -1 : (int32_t)count;
3691 // GetChildAt_Deprecated will return nullptr if our index < 0 or index >=
3692 // count
3693 nsCOMPtr<nsIContent> areaContent;
3694 while ((areaContent = mapContent->GetChildAt_Deprecated(
3695 aForward ? ++index : --index)) != nullptr) {
3696 if (areaContent->IsFocusable(&tabIndex) && tabIndex == aCurrentTabIndex) {
3697 return areaContent;
3702 return nullptr;
3705 int32_t nsFocusManager::GetNextTabIndex(nsIContent* aParent,
3706 int32_t aCurrentTabIndex,
3707 bool aForward) {
3708 int32_t tabIndex, childTabIndex;
3709 StyleChildrenIterator iter(aParent);
3711 if (aForward) {
3712 tabIndex = 0;
3713 for (nsIContent* child = iter.GetNextChild(); child;
3714 child = iter.GetNextChild()) {
3715 // Skip child's descendants if child is a shadow host or slot, as they are
3716 // in the focus navigation scope owned by child's shadow root
3717 if (!IsHostOrSlot(child)) {
3718 childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
3719 if (childTabIndex > aCurrentTabIndex && childTabIndex != tabIndex) {
3720 tabIndex = (tabIndex == 0 || childTabIndex < tabIndex) ? childTabIndex
3721 : tabIndex;
3725 nsAutoString tabIndexStr;
3726 if (child->IsElement()) {
3727 child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex,
3728 tabIndexStr);
3730 nsresult ec;
3731 int32_t val = tabIndexStr.ToInteger(&ec);
3732 if (NS_SUCCEEDED(ec) && val > aCurrentTabIndex && val != tabIndex) {
3733 tabIndex = (tabIndex == 0 || val < tabIndex) ? val : tabIndex;
3736 } else { /* !aForward */
3737 tabIndex = 1;
3738 for (nsIContent* child = iter.GetNextChild(); child;
3739 child = iter.GetNextChild()) {
3740 // Skip child's descendants if child is a shadow host or slot, as they are
3741 // in the focus navigation scope owned by child's shadow root
3742 if (!IsHostOrSlot(child)) {
3743 childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
3744 if ((aCurrentTabIndex == 0 && childTabIndex > tabIndex) ||
3745 (childTabIndex < aCurrentTabIndex && childTabIndex > tabIndex)) {
3746 tabIndex = childTabIndex;
3750 nsAutoString tabIndexStr;
3751 if (child->IsElement()) {
3752 child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex,
3753 tabIndexStr);
3755 nsresult ec;
3756 int32_t val = tabIndexStr.ToInteger(&ec);
3757 if (NS_SUCCEEDED(ec)) {
3758 if ((aCurrentTabIndex == 0 && val > tabIndex) ||
3759 (val < aCurrentTabIndex && val > tabIndex)) {
3760 tabIndex = val;
3766 return tabIndex;
3769 nsresult nsFocusManager::FocusFirst(Element* aRootElement,
3770 nsIContent** aNextContent) {
3771 if (!aRootElement) {
3772 return NS_OK;
3775 Document* doc = aRootElement->GetComposedDoc();
3776 if (doc) {
3777 if (nsContentUtils::IsChromeDoc(doc)) {
3778 // If the redirectdocumentfocus attribute is set, redirect the focus to a
3779 // specific element. This is primarily used to retarget the focus to the
3780 // urlbar during document navigation.
3781 nsAutoString retarget;
3783 if (aRootElement->GetAttr(kNameSpaceID_None,
3784 nsGkAtoms::retargetdocumentfocus, retarget)) {
3785 nsCOMPtr<Element> element = doc->GetElementById(retarget);
3786 nsCOMPtr<nsIContent> retargetElement =
3787 FlushAndCheckIfFocusable(element, 0);
3788 if (retargetElement) {
3789 retargetElement.forget(aNextContent);
3790 return NS_OK;
3795 nsCOMPtr<nsIDocShell> docShell = doc->GetDocShell();
3796 if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
3797 // If the found content is in a chrome shell, navigate forward one
3798 // tabbable item so that the first item is focused. Note that we
3799 // always go forward and not back here.
3800 PresShell* presShell = doc->GetPresShell();
3801 if (presShell) {
3802 return GetNextTabbableContent(presShell, aRootElement, nullptr,
3803 aRootElement, true, 1, false, false,
3804 aNextContent);
3809 NS_ADDREF(*aNextContent = aRootElement);
3810 return NS_OK;
3813 Element* nsFocusManager::GetRootForFocus(nsPIDOMWindowOuter* aWindow,
3814 Document* aDocument,
3815 bool aForDocumentNavigation,
3816 bool aCheckVisibility) {
3817 if (!aForDocumentNavigation) {
3818 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
3819 if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
3820 return nullptr;
3824 if (aCheckVisibility && !IsWindowVisible(aWindow)) return nullptr;
3826 // If the body is contenteditable, use the editor's root element rather than
3827 // the actual root element.
3828 RefPtr<Element> rootElement =
3829 nsLayoutUtils::GetEditableRootContentByContentEditable(aDocument);
3830 if (!rootElement || !rootElement->GetPrimaryFrame()) {
3831 rootElement = aDocument->GetRootElement();
3832 if (!rootElement) {
3833 return nullptr;
3837 if (aCheckVisibility && !rootElement->GetPrimaryFrame()) {
3838 return nullptr;
3841 // Finally, check if this is a frameset
3842 nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(aDocument);
3843 if (htmlDoc) {
3844 Element* htmlChild = aDocument->GetHtmlChildElement(nsGkAtoms::frameset);
3845 if (htmlChild) {
3846 // In document navigation mode, return the frameset so that navigation
3847 // descends into the child frames.
3848 return aForDocumentNavigation ? htmlChild : nullptr;
3852 return rootElement;
3855 Element* nsFocusManager::GetRootForChildDocument(nsIContent* aContent) {
3856 // Check for elements that represent child documents, that is, browsers,
3857 // editors or frames from a frameset. We don't include iframes since we
3858 // consider them to be an integral part of the same window or page.
3859 if (!aContent || !(aContent->IsXULElement(nsGkAtoms::browser) ||
3860 aContent->IsXULElement(nsGkAtoms::editor) ||
3861 aContent->IsHTMLElement(nsGkAtoms::frame))) {
3862 return nullptr;
3865 Document* doc = aContent->GetComposedDoc();
3866 if (!doc) {
3867 return nullptr;
3870 Document* subdoc = doc->GetSubDocumentFor(aContent);
3871 if (!subdoc || subdoc->EventHandlingSuppressed()) {
3872 return nullptr;
3875 nsCOMPtr<nsPIDOMWindowOuter> window = subdoc->GetWindow();
3876 return GetRootForFocus(window, subdoc, true, true);
3879 void nsFocusManager::GetFocusInSelection(nsPIDOMWindowOuter* aWindow,
3880 nsIContent* aStartSelection,
3881 nsIContent* aEndSelection,
3882 nsIContent** aFocusedContent) {
3883 *aFocusedContent = nullptr;
3885 nsCOMPtr<nsIContent> testContent = aStartSelection;
3886 nsCOMPtr<nsIContent> nextTestContent = aEndSelection;
3888 nsCOMPtr<nsIContent> currentFocus = aWindow->GetFocusedElement();
3890 // We now have the correct start node in selectionContent!
3891 // Search for focusable elements, starting with selectionContent
3893 // Method #1: Keep going up while we look - an ancestor might be focusable
3894 // We could end the loop earlier, such as when we're no longer
3895 // in the same frame, by comparing selectionContent->GetPrimaryFrame()
3896 // with a variable holding the starting selectionContent
3897 while (testContent) {
3898 // Keep testing while selectionContent is equal to something,
3899 // eventually we'll run out of ancestors
3901 nsCOMPtr<nsIURI> uri;
3902 if (testContent == currentFocus ||
3903 testContent->IsLink(getter_AddRefs(uri))) {
3904 testContent.forget(aFocusedContent);
3905 return;
3908 // Get the parent
3909 testContent = testContent->GetParent();
3911 if (!testContent) {
3912 // We run this loop again, checking the ancestor chain of the selection's
3913 // end point
3914 testContent = nextTestContent;
3915 nextTestContent = nullptr;
3919 // We couldn't find an anchor that was an ancestor of the selection start
3920 // Method #2: look for anchor in selection's primary range (depth first
3921 // search)
3923 nsCOMPtr<nsIContent> selectionNode = aStartSelection;
3924 nsCOMPtr<nsIContent> endSelectionNode = aEndSelection;
3925 nsCOMPtr<nsIContent> testNode;
3927 do {
3928 testContent = selectionNode;
3930 // We're looking for any focusable link that could be part of the
3931 // main document's selection.
3932 nsCOMPtr<nsIURI> uri;
3933 if (testContent == currentFocus ||
3934 testContent->IsLink(getter_AddRefs(uri))) {
3935 testContent.forget(aFocusedContent);
3936 return;
3939 nsIContent* testNode = selectionNode->GetFirstChild();
3940 if (testNode) {
3941 selectionNode = testNode;
3942 continue;
3945 if (selectionNode == endSelectionNode) break;
3946 testNode = selectionNode->GetNextSibling();
3947 if (testNode) {
3948 selectionNode = testNode;
3949 continue;
3952 do {
3953 // GetParent is OK here, instead of GetParentNode, because the only case
3954 // where the latter returns something different from the former is when
3955 // GetParentNode is the document. But in that case we would simply get
3956 // null for selectionNode when setting it to testNode->GetNextSibling()
3957 // (because a document has no next sibling). And then the next iteration
3958 // of this loop would get null for GetParentNode anyway, and break out of
3959 // all the loops.
3960 testNode = selectionNode->GetParent();
3961 if (!testNode || testNode == endSelectionNode) {
3962 selectionNode = nullptr;
3963 break;
3965 selectionNode = testNode->GetNextSibling();
3966 if (selectionNode) break;
3967 selectionNode = testNode;
3968 } while (true);
3969 } while (selectionNode && selectionNode != endSelectionNode);
3972 class PointerUnlocker : public Runnable {
3973 public:
3974 PointerUnlocker() : mozilla::Runnable("PointerUnlocker") {
3975 MOZ_ASSERT(!PointerUnlocker::sActiveUnlocker);
3976 PointerUnlocker::sActiveUnlocker = this;
3979 ~PointerUnlocker() {
3980 if (PointerUnlocker::sActiveUnlocker == this) {
3981 PointerUnlocker::sActiveUnlocker = nullptr;
3985 NS_IMETHOD Run() override {
3986 if (PointerUnlocker::sActiveUnlocker == this) {
3987 PointerUnlocker::sActiveUnlocker = nullptr;
3989 NS_ENSURE_STATE(nsFocusManager::GetFocusManager());
3990 nsPIDOMWindowOuter* focused =
3991 nsFocusManager::GetFocusManager()->GetFocusedWindow();
3992 nsCOMPtr<Document> pointerLockedDoc =
3993 do_QueryReferent(EventStateManager::sPointerLockedDoc);
3994 if (pointerLockedDoc && !nsContentUtils::IsInPointerLockContext(focused)) {
3995 Document::UnlockPointer();
3997 return NS_OK;
4000 static PointerUnlocker* sActiveUnlocker;
4003 PointerUnlocker* PointerUnlocker::sActiveUnlocker = nullptr;
4005 void nsFocusManager::SetFocusedWindowInternal(nsPIDOMWindowOuter* aWindow) {
4006 if (!PointerUnlocker::sActiveUnlocker &&
4007 nsContentUtils::IsInPointerLockContext(mFocusedWindow) &&
4008 !nsContentUtils::IsInPointerLockContext(aWindow)) {
4009 nsCOMPtr<nsIRunnable> runnable = new PointerUnlocker();
4010 NS_DispatchToCurrentThread(runnable);
4013 // Update the last focus time on any affected documents
4014 if (aWindow && aWindow != mFocusedWindow) {
4015 const TimeStamp now(TimeStamp::Now());
4016 for (Document* doc = aWindow->GetExtantDoc(); doc;
4017 doc = doc->GetParentDocument()) {
4018 doc->SetLastFocusTime(now);
4022 mFocusedWindow = aWindow;
4025 void nsFocusManager::MarkUncollectableForCCGeneration(uint32_t aGeneration) {
4026 if (!sInstance) {
4027 return;
4030 if (sInstance->mActiveWindow) {
4031 sInstance->mActiveWindow->MarkUncollectableForCCGeneration(aGeneration);
4033 if (sInstance->mFocusedWindow) {
4034 sInstance->mFocusedWindow->MarkUncollectableForCCGeneration(aGeneration);
4036 if (sInstance->mWindowBeingLowered) {
4037 sInstance->mWindowBeingLowered->MarkUncollectableForCCGeneration(
4038 aGeneration);
4040 if (sInstance->mFocusedElement) {
4041 sInstance->mFocusedElement->OwnerDoc()->MarkUncollectableForCCGeneration(
4042 aGeneration);
4044 if (sInstance->mFirstBlurEvent) {
4045 sInstance->mFirstBlurEvent->OwnerDoc()->MarkUncollectableForCCGeneration(
4046 aGeneration);
4048 if (sInstance->mFirstFocusEvent) {
4049 sInstance->mFirstFocusEvent->OwnerDoc()->MarkUncollectableForCCGeneration(
4050 aGeneration);
4052 if (sInstance->mMouseButtonEventHandlingDocument) {
4053 sInstance->mMouseButtonEventHandlingDocument
4054 ->MarkUncollectableForCCGeneration(aGeneration);
4058 bool nsFocusManager::CanSkipFocus(nsIContent* aContent) {
4059 if (!aContent || nsContentUtils::IsChromeDoc(aContent->OwnerDoc())) {
4060 return false;
4063 if (mFocusedElement == aContent) {
4064 return true;
4067 nsIDocShell* ds = aContent->OwnerDoc()->GetDocShell();
4068 if (!ds) {
4069 return true;
4072 nsCOMPtr<nsIDocShellTreeItem> root;
4073 ds->GetRootTreeItem(getter_AddRefs(root));
4074 nsCOMPtr<nsPIDOMWindowOuter> newRootWindow =
4075 root ? root->GetWindow() : nullptr;
4076 if (mActiveWindow != newRootWindow) {
4077 nsPIDOMWindowOuter* outerWindow = aContent->OwnerDoc()->GetWindow();
4078 if (outerWindow && outerWindow->GetFocusedElement() == aContent) {
4079 return true;
4083 return false;
4086 nsresult NS_NewFocusManager(nsIFocusManager** aResult) {
4087 NS_IF_ADDREF(*aResult = nsFocusManager::GetFocusManager());
4088 return NS_OK;