nsFocusManager.cpp (219746B)
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/. */ 6 7 #include "nsFocusManager.h" 8 9 #include <algorithm> 10 11 #include "AncestorIterator.h" 12 #include "BrowserChild.h" 13 #include "ChildIterator.h" 14 #include "ContentParent.h" 15 #include "LayoutConstants.h" 16 #include "mozilla/AccessibleCaretEventHub.h" 17 #include "mozilla/ContentEvents.h" 18 #include "mozilla/EventDispatcher.h" 19 #include "mozilla/EventStateManager.h" 20 #include "mozilla/FocusModel.h" 21 #include "mozilla/HTMLEditor.h" 22 #include "mozilla/IMEStateManager.h" 23 #include "mozilla/LookAndFeel.h" 24 #include "mozilla/Maybe.h" 25 #include "mozilla/PointerLockManager.h" 26 #include "mozilla/Preferences.h" 27 #include "mozilla/PresShell.h" 28 #include "mozilla/Services.h" 29 #include "mozilla/StaticPrefs_accessibility.h" 30 #include "mozilla/StaticPrefs_full_screen_api.h" 31 #include "mozilla/dom/BrowserBridgeChild.h" 32 #include "mozilla/dom/BrowserParent.h" 33 #include "mozilla/dom/ContentChild.h" 34 #include "mozilla/dom/Document.h" 35 #include "mozilla/dom/DocumentInlines.h" 36 #include "mozilla/dom/Element.h" 37 #include "mozilla/dom/ElementBinding.h" 38 #include "mozilla/dom/HTMLAreaElement.h" 39 #include "mozilla/dom/HTMLImageElement.h" 40 #include "mozilla/dom/HTMLInputElement.h" 41 #include "mozilla/dom/HTMLSlotElement.h" 42 #include "mozilla/dom/Navigation.h" 43 #include "mozilla/dom/Selection.h" 44 #include "mozilla/dom/Text.h" 45 #include "mozilla/dom/WindowGlobalChild.h" 46 #include "mozilla/dom/WindowGlobalParent.h" 47 #include "mozilla/dom/XULPopupElement.h" 48 #include "mozilla/widget/IMEData.h" 49 #include "nsCaret.h" 50 #include "nsContentUtils.h" 51 #include "nsFrameLoader.h" 52 #include "nsFrameLoaderOwner.h" 53 #include "nsFrameSelection.h" 54 #include "nsFrameTraversal.h" 55 #include "nsGkAtoms.h" 56 #include "nsHTMLDocument.h" 57 #include "nsIAppWindow.h" 58 #include "nsIBaseWindow.h" 59 #include "nsIContentInlines.h" 60 #include "nsIDOMXULMenuListElement.h" 61 #include "nsIDocShell.h" 62 #include "nsIDocShellTreeOwner.h" 63 #include "nsIFormControl.h" 64 #include "nsIInterfaceRequestorUtils.h" 65 #include "nsIObserverService.h" 66 #include "nsIPrincipal.h" 67 #include "nsIScriptError.h" 68 #include "nsIScriptObjectPrincipal.h" 69 #include "nsIWebNavigation.h" 70 #include "nsIXULRuntime.h" 71 #include "nsLayoutUtils.h" 72 #include "nsMenuPopupFrame.h" 73 #include "nsNetUtil.h" 74 #include "nsPIDOMWindow.h" 75 #include "nsQueryObject.h" 76 #include "nsRange.h" 77 #include "nsTextControlFrame.h" 78 #include "nsThreadUtils.h" 79 #include "nsXULPopupManager.h" 80 81 #ifdef ACCESSIBILITY 82 # include "nsAccessibilityService.h" 83 #endif 84 85 using namespace mozilla; 86 using namespace mozilla::dom; 87 using namespace mozilla::widget; 88 89 // Two types of focus pr logging are available: 90 // 'Focus' for normal focus manager calls 91 // 'FocusNavigation' for tab and document navigation 92 LazyLogModule gFocusLog("Focus"); 93 LazyLogModule gFocusNavigationLog("FocusNavigation"); 94 95 #define LOGFOCUS(args) MOZ_LOG(gFocusLog, mozilla::LogLevel::Debug, args) 96 #define LOGFOCUSNAVIGATION(args) \ 97 MOZ_LOG(gFocusNavigationLog, mozilla::LogLevel::Debug, args) 98 99 #define LOGTAG(log, format, content) \ 100 if (MOZ_LOG_TEST(log, LogLevel::Debug)) { \ 101 nsAutoCString tag("(none)"_ns); \ 102 if (content) { \ 103 content->NodeInfo()->NameAtom()->ToUTF8String(tag); \ 104 } \ 105 MOZ_LOG(log, LogLevel::Debug, (format, tag.get())); \ 106 } 107 108 #define LOGCONTENT(format, content) LOGTAG(gFocusLog, format, content) 109 #define LOGCONTENTNAVIGATION(format, content) \ 110 LOGTAG(gFocusNavigationLog, format, content) 111 112 struct nsDelayedBlurOrFocusEvent { 113 nsDelayedBlurOrFocusEvent(EventMessage aEventMessage, PresShell* aPresShell, 114 Document* aDocument, EventTarget* aTarget, 115 EventTarget* aRelatedTarget) 116 : mPresShell(aPresShell), 117 mDocument(aDocument), 118 mTarget(aTarget), 119 mEventMessage(aEventMessage), 120 mRelatedTarget(aRelatedTarget) {} 121 122 nsDelayedBlurOrFocusEvent(const nsDelayedBlurOrFocusEvent& aOther) 123 : mPresShell(aOther.mPresShell), 124 mDocument(aOther.mDocument), 125 mTarget(aOther.mTarget), 126 mEventMessage(aOther.mEventMessage) {} 127 128 RefPtr<PresShell> mPresShell; 129 nsCOMPtr<Document> mDocument; 130 nsCOMPtr<EventTarget> mTarget; 131 EventMessage mEventMessage; 132 nsCOMPtr<EventTarget> mRelatedTarget; 133 }; 134 135 inline void ImplCycleCollectionUnlink(nsDelayedBlurOrFocusEvent& aField) { 136 aField.mPresShell = nullptr; 137 aField.mDocument = nullptr; 138 aField.mTarget = nullptr; 139 aField.mRelatedTarget = nullptr; 140 } 141 142 inline void ImplCycleCollectionTraverse( 143 nsCycleCollectionTraversalCallback& aCallback, 144 nsDelayedBlurOrFocusEvent& aField, const char* aName, uint32_t aFlags = 0) { 145 CycleCollectionNoteChild( 146 aCallback, static_cast<nsIDocumentObserver*>(aField.mPresShell.get()), 147 aName, aFlags); 148 CycleCollectionNoteChild(aCallback, aField.mDocument.get(), aName, aFlags); 149 CycleCollectionNoteChild(aCallback, aField.mTarget.get(), aName, aFlags); 150 CycleCollectionNoteChild(aCallback, aField.mRelatedTarget.get(), aName, 151 aFlags); 152 } 153 154 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFocusManager) 155 NS_INTERFACE_MAP_ENTRY(nsIFocusManager) 156 NS_INTERFACE_MAP_ENTRY(nsIObserver) 157 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) 158 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFocusManager) 159 NS_INTERFACE_MAP_END 160 161 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFocusManager) 162 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFocusManager) 163 164 NS_IMPL_CYCLE_COLLECTION_WEAK(nsFocusManager, mActiveWindow, 165 mActiveBrowsingContextInContent, 166 mActiveBrowsingContextInChrome, mFocusedWindow, 167 mFocusedBrowsingContextInContent, 168 mFocusedBrowsingContextInChrome, mFocusedElement, 169 mWindowBeingLowered, mDelayedBlurFocusEvents) 170 171 StaticRefPtr<nsFocusManager> nsFocusManager::sInstance; 172 bool nsFocusManager::sTestMode = false; 173 uint64_t nsFocusManager::sFocusActionCounter = 0; 174 175 static const char* kObservedPrefs[] = {"accessibility.browsewithcaret", 176 "focusmanager.testmode", nullptr}; 177 178 nsFocusManager::nsFocusManager() 179 : mActionIdForActiveBrowsingContextInContent(0), 180 mActionIdForActiveBrowsingContextInChrome(0), 181 mActionIdForFocusedBrowsingContextInContent(0), 182 mActionIdForFocusedBrowsingContextInChrome(0), 183 mActiveBrowsingContextInContentSetFromOtherProcess(false), 184 mEventHandlingNeedsFlush(false) {} 185 186 nsFocusManager::~nsFocusManager() { 187 Preferences::UnregisterCallbacks(nsFocusManager::PrefChanged, kObservedPrefs, 188 this); 189 190 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 191 if (obs) { 192 obs->RemoveObserver(this, "xpcom-shutdown"); 193 } 194 } 195 196 // static 197 nsresult nsFocusManager::Init() { 198 sInstance = new nsFocusManager(); 199 200 sTestMode = Preferences::GetBool("focusmanager.testmode", false); 201 202 Preferences::RegisterCallbacks(nsFocusManager::PrefChanged, kObservedPrefs, 203 sInstance.get()); 204 205 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 206 if (obs) { 207 obs->AddObserver(sInstance, "xpcom-shutdown", true); 208 } 209 210 return NS_OK; 211 } 212 213 // static 214 void nsFocusManager::Shutdown() { sInstance = nullptr; } 215 216 // static 217 void nsFocusManager::PrefChanged(const char* aPref, void* aSelf) { 218 if (RefPtr<nsFocusManager> fm = static_cast<nsFocusManager*>(aSelf)) { 219 fm->PrefChanged(aPref); 220 } 221 } 222 223 void nsFocusManager::PrefChanged(const char* aPref) { 224 nsDependentCString pref(aPref); 225 if (pref.EqualsLiteral("accessibility.browsewithcaret")) { 226 UpdateCaretForCaretBrowsingMode(); 227 } else if (pref.EqualsLiteral("focusmanager.testmode")) { 228 sTestMode = Preferences::GetBool("focusmanager.testmode", false); 229 } 230 } 231 232 NS_IMETHODIMP 233 nsFocusManager::Observe(nsISupports* aSubject, const char* aTopic, 234 const char16_t* aData) { 235 if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) { 236 mActiveWindow = nullptr; 237 mActiveBrowsingContextInContent = nullptr; 238 mActionIdForActiveBrowsingContextInContent = 0; 239 mActionIdForFocusedBrowsingContextInContent = 0; 240 mActiveBrowsingContextInChrome = nullptr; 241 mActionIdForActiveBrowsingContextInChrome = 0; 242 mActionIdForFocusedBrowsingContextInChrome = 0; 243 mFocusedWindow = nullptr; 244 mFocusedBrowsingContextInContent = nullptr; 245 mFocusedBrowsingContextInChrome = nullptr; 246 mFocusedElement = nullptr; 247 mWindowBeingLowered = nullptr; 248 mDelayedBlurFocusEvents.Clear(); 249 } 250 251 return NS_OK; 252 } 253 254 static bool ActionIdComparableAndLower(uint64_t aActionId, 255 uint64_t aReference) { 256 MOZ_ASSERT(aActionId, "Uninitialized action id"); 257 auto [actionProc, actionId] = 258 nsContentUtils::SplitProcessSpecificId(aActionId); 259 auto [refProc, refId] = nsContentUtils::SplitProcessSpecificId(aReference); 260 return actionProc == refProc && actionId < refId; 261 } 262 263 // given a frame content node, retrieve the nsIDOMWindow displayed in it 264 static nsPIDOMWindowOuter* GetContentWindow(nsIContent* aContent) { 265 if (Document* doc = aContent->GetComposedDoc()) { 266 if (Document* subdoc = doc->GetSubDocumentFor(aContent)) { 267 return subdoc->GetWindow(); 268 } 269 } 270 return nullptr; 271 } 272 273 bool nsFocusManager::IsFocused(nsIContent* aContent) { 274 if (!aContent || !mFocusedElement) { 275 return false; 276 } 277 return aContent == mFocusedElement; 278 } 279 280 bool nsFocusManager::IsTestMode() { return sTestMode; } 281 282 bool nsFocusManager::IsInActiveWindow(BrowsingContext* aBC) const { 283 RefPtr<BrowsingContext> top = aBC->Top(); 284 if (XRE_IsParentProcess()) { 285 top = top->Canonical()->TopCrossChromeBoundary(); 286 } 287 return IsSameOrAncestor(top, GetActiveBrowsingContext()); 288 } 289 290 // get the current window for the given content node 291 static nsPIDOMWindowOuter* GetCurrentWindow(nsIContent* aContent) { 292 Document* doc = aContent->GetComposedDoc(); 293 return doc ? doc->GetWindow() : nullptr; 294 } 295 296 // static 297 Element* nsFocusManager::GetFocusedDescendant( 298 nsPIDOMWindowOuter* aWindow, SearchRange aSearchRange, 299 nsPIDOMWindowOuter** aFocusedWindow) { 300 NS_ENSURE_TRUE(aWindow, nullptr); 301 302 *aFocusedWindow = nullptr; 303 304 Element* currentElement = nullptr; 305 nsPIDOMWindowOuter* window = aWindow; 306 for (;;) { 307 *aFocusedWindow = window; 308 currentElement = window->GetFocusedElement(); 309 if (!currentElement || aSearchRange == eOnlyCurrentWindow) { 310 break; 311 } 312 313 window = GetContentWindow(currentElement); 314 if (!window) { 315 break; 316 } 317 318 if (aSearchRange == eIncludeAllDescendants) { 319 continue; 320 } 321 322 MOZ_ASSERT(aSearchRange == eIncludeVisibleDescendants); 323 324 // If the child window doesn't have PresShell, it means the window is 325 // invisible. 326 nsIDocShell* docShell = window->GetDocShell(); 327 if (!docShell) { 328 break; 329 } 330 if (!docShell->GetPresShell()) { 331 break; 332 } 333 } 334 335 NS_IF_ADDREF(*aFocusedWindow); 336 337 return currentElement; 338 } 339 340 // static 341 InputContextAction::Cause nsFocusManager::GetFocusMoveActionCause( 342 uint32_t aFlags) { 343 if (aFlags & nsIFocusManager::FLAG_BYTOUCH) { 344 return InputContextAction::CAUSE_TOUCH; 345 } else if (aFlags & nsIFocusManager::FLAG_BYMOUSE) { 346 return InputContextAction::CAUSE_MOUSE; 347 } else if (aFlags & nsIFocusManager::FLAG_BYKEY) { 348 return InputContextAction::CAUSE_KEY; 349 } else if (aFlags & nsIFocusManager::FLAG_BYLONGPRESS) { 350 return InputContextAction::CAUSE_LONGPRESS; 351 } 352 return InputContextAction::CAUSE_UNKNOWN; 353 } 354 355 NS_IMETHODIMP 356 nsFocusManager::GetActiveWindow(mozIDOMWindowProxy** aWindow) { 357 MOZ_ASSERT(XRE_IsParentProcess(), 358 "Must not be called outside the parent process."); 359 NS_IF_ADDREF(*aWindow = mActiveWindow); 360 return NS_OK; 361 } 362 363 NS_IMETHODIMP 364 nsFocusManager::GetActiveBrowsingContext(BrowsingContext** aBrowsingContext) { 365 NS_IF_ADDREF(*aBrowsingContext = GetActiveBrowsingContext()); 366 return NS_OK; 367 } 368 369 void nsFocusManager::FocusWindow(nsPIDOMWindowOuter* aWindow, 370 CallerType aCallerType) { 371 if (RefPtr<nsFocusManager> fm = sInstance) { 372 fm->SetFocusedWindowWithCallerType(aWindow, aCallerType); 373 } 374 } 375 376 NS_IMETHODIMP 377 nsFocusManager::GetFocusedWindow(mozIDOMWindowProxy** aFocusedWindow) { 378 NS_IF_ADDREF(*aFocusedWindow = mFocusedWindow); 379 return NS_OK; 380 } 381 382 NS_IMETHODIMP 383 nsFocusManager::GetFocusedContentBrowsingContext( 384 BrowsingContext** aBrowsingContext) { 385 MOZ_DIAGNOSTIC_ASSERT( 386 XRE_IsParentProcess(), 387 "We only have use cases for this in the parent process"); 388 NS_IF_ADDREF(*aBrowsingContext = GetFocusedBrowsingContextInChrome()); 389 return NS_OK; 390 } 391 392 NS_IMETHODIMP 393 nsFocusManager::GetActiveContentBrowsingContext( 394 BrowsingContext** aBrowsingContext) { 395 MOZ_DIAGNOSTIC_ASSERT( 396 XRE_IsParentProcess(), 397 "We only have use cases for this in the parent process"); 398 NS_IF_ADDREF(*aBrowsingContext = GetActiveBrowsingContextInChrome()); 399 return NS_OK; 400 } 401 402 nsresult nsFocusManager::SetFocusedWindowWithCallerType( 403 mozIDOMWindowProxy* aWindowToFocus, CallerType aCallerType) { 404 LOGFOCUS(("<<SetFocusedWindow begin>>")); 405 406 nsCOMPtr<nsPIDOMWindowOuter> windowToFocus = 407 nsPIDOMWindowOuter::From(aWindowToFocus); 408 NS_ENSURE_TRUE(windowToFocus, NS_ERROR_FAILURE); 409 410 nsCOMPtr<Element> frameElement = windowToFocus->GetFrameElementInternal(); 411 Maybe<uint64_t> existingActionId; 412 if (frameElement) { 413 // pass false for aFocusChanged so that the caret does not get updated 414 // and scrolling does not occur. 415 existingActionId = SetFocusInner(frameElement, 0, false, true); 416 } else if (auto* bc = windowToFocus->GetBrowsingContext(); 417 bc && !bc->IsTop()) { 418 // No frameElement means windowToFocus is an OOP iframe, so 419 // the above SetFocusInner is not called. That means the focus 420 // of the currently focused BC is not going to be cleared. So 421 // we do that manually here. 422 if (RefPtr<BrowsingContext> focusedBC = GetFocusedBrowsingContext()) { 423 // If focusedBC is an ancestor of bc, blur will be handled 424 // correctly by nsFocusManager::AdjustWindowFocus. 425 if (!IsSameOrAncestor(focusedBC, bc)) { 426 existingActionId.emplace(sInstance->GenerateFocusActionId()); 427 Blur(focusedBC, nullptr, true, true, false, existingActionId.value()); 428 } 429 } 430 } else { 431 // this is a top-level window. If the window has a child frame focused, 432 // clear the focus. Otherwise, focus should already be in this frame, or 433 // already cleared. This ensures that focus will be in this frame and not 434 // in a child. 435 if (Element* el = windowToFocus->GetFocusedElement()) { 436 if (nsCOMPtr<nsPIDOMWindowOuter> childWindow = GetContentWindow(el)) { 437 ClearFocus(windowToFocus); 438 } 439 } 440 } 441 442 nsCOMPtr<nsPIDOMWindowOuter> rootWindow = windowToFocus->GetPrivateRoot(); 443 const uint64_t actionId = existingActionId.isSome() 444 ? existingActionId.value() 445 : sInstance->GenerateFocusActionId(); 446 if (rootWindow) { 447 RaiseWindow(rootWindow, aCallerType, actionId); 448 } 449 450 LOGFOCUS(("<<SetFocusedWindow end actionid: %" PRIu64 ">>", actionId)); 451 452 return NS_OK; 453 } 454 455 NS_IMETHODIMP nsFocusManager::SetFocusedWindow( 456 mozIDOMWindowProxy* aWindowToFocus) { 457 return SetFocusedWindowWithCallerType(aWindowToFocus, CallerType::System); 458 } 459 460 NS_IMETHODIMP 461 nsFocusManager::GetFocusedElement(Element** aFocusedElement) { 462 RefPtr<Element> focusedElement = mFocusedElement; 463 focusedElement.forget(aFocusedElement); 464 return NS_OK; 465 } 466 467 uint32_t nsFocusManager::GetLastFocusMethod(nsPIDOMWindowOuter* aWindow) const { 468 nsPIDOMWindowOuter* window = aWindow ? aWindow : mFocusedWindow.get(); 469 uint32_t method = window ? window->GetFocusMethod() : 0; 470 NS_ASSERTION((method & METHOD_MASK) == method, "invalid focus method"); 471 return method; 472 } 473 474 NS_IMETHODIMP 475 nsFocusManager::GetLastFocusMethod(mozIDOMWindowProxy* aWindow, 476 uint32_t* aLastFocusMethod) { 477 *aLastFocusMethod = GetLastFocusMethod(nsPIDOMWindowOuter::From(aWindow)); 478 return NS_OK; 479 } 480 481 NS_IMETHODIMP 482 nsFocusManager::SetFocus(Element* aElement, uint32_t aFlags) { 483 LOGFOCUS(("<<SetFocus begin>>")); 484 485 NS_ENSURE_ARG(aElement); 486 487 SetFocusInner(aElement, aFlags, true, true); 488 489 LOGFOCUS(("<<SetFocus end>>")); 490 491 return NS_OK; 492 } 493 494 NS_IMETHODIMP 495 nsFocusManager::ElementIsFocusable(Element* aElement, uint32_t aFlags, 496 bool* aIsFocusable) { 497 NS_ENSURE_TRUE(aElement, NS_ERROR_INVALID_ARG); 498 *aIsFocusable = !!FlushAndCheckIfFocusable(aElement, aFlags); 499 return NS_OK; 500 } 501 502 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP 503 nsFocusManager::MoveFocus(mozIDOMWindowProxy* aWindow, Element* aStartElement, 504 uint32_t aType, uint32_t aFlags, Element** aElement) { 505 *aElement = nullptr; 506 507 LOGFOCUS(("<<MoveFocus begin Type: %d Flags: %x>>", aType, aFlags)); 508 509 if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug) && mFocusedWindow) { 510 Document* doc = mFocusedWindow->GetExtantDoc(); 511 if (doc && doc->GetDocumentURI()) { 512 LOGFOCUS((" Focused Window: %p %s", mFocusedWindow.get(), 513 doc->GetDocumentURI()->GetSpecOrDefault().get())); 514 } 515 } 516 517 LOGCONTENT(" Current Focus: %s", mFocusedElement.get()); 518 519 // use FLAG_BYMOVEFOCUS when switching focus with MoveFocus unless one of 520 // the other focus methods is already set, or we're just moving to the root 521 // or caret position. 522 if (aType != MOVEFOCUS_ROOT && aType != MOVEFOCUS_CARET && 523 (aFlags & METHOD_MASK) == 0) { 524 aFlags |= FLAG_BYMOVEFOCUS; 525 } 526 527 nsCOMPtr<nsPIDOMWindowOuter> window; 528 if (aStartElement) { 529 window = GetCurrentWindow(aStartElement); 530 } else { 531 window = aWindow ? nsPIDOMWindowOuter::From(aWindow) : mFocusedWindow.get(); 532 } 533 534 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); 535 536 // Flush to ensure that focusability of descendants is computed correctly. 537 if (RefPtr<Document> doc = window->GetExtantDoc()) { 538 doc->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames); 539 } 540 541 bool noParentTraversal = aFlags & FLAG_NOPARENTFRAME; 542 nsCOMPtr<nsIContent> newFocus; 543 nsresult rv = DetermineElementToMoveFocus(window, aStartElement, aType, 544 noParentTraversal, true, 545 getter_AddRefs(newFocus)); 546 if (rv == NS_SUCCESS_DOM_NO_OPERATION) { 547 return NS_OK; 548 } 549 550 NS_ENSURE_SUCCESS(rv, rv); 551 552 LOGCONTENTNAVIGATION("Element to be focused: %s", newFocus.get()); 553 554 if (newFocus && newFocus->IsElement()) { 555 // for caret movement, pass false for the aFocusChanged argument, 556 // otherwise the caret will end up moving to the focus position. This 557 // would be a problem because the caret would move to the beginning of the 558 // focused link making it impossible to navigate the caret over a link. 559 SetFocusInner(MOZ_KnownLive(newFocus->AsElement()), aFlags, 560 aType != MOVEFOCUS_CARET, true); 561 *aElement = do_AddRef(newFocus->AsElement()).take(); 562 } else if (aType == MOVEFOCUS_ROOT || aType == MOVEFOCUS_CARET) { 563 // no content was found, so clear the focus for these two types. 564 ClearFocus(window); 565 } 566 567 LOGFOCUS(("<<MoveFocus end>>")); 568 569 return NS_OK; 570 } 571 572 NS_IMETHODIMP 573 nsFocusManager::ClearFocus(mozIDOMWindowProxy* aWindow) { 574 LOGFOCUS(("<<ClearFocus begin>>")); 575 576 // if the window to clear is the focused window or an ancestor of the 577 // focused window, then blur the existing focused content. Otherwise, the 578 // focus is somewhere else so just update the current node. 579 NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG); 580 nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow); 581 582 if (IsSameOrAncestor(window, GetFocusedBrowsingContext())) { 583 RefPtr<BrowsingContext> bc = window->GetBrowsingContext(); 584 RefPtr<BrowsingContext> focusedBC = GetFocusedBrowsingContext(); 585 const bool isAncestor = (focusedBC != bc); 586 RefPtr<BrowsingContext> ancestorBC = isAncestor ? bc : nullptr; 587 if (Blur(focusedBC, ancestorBC, isAncestor, true, false, 588 GenerateFocusActionId())) { 589 // if we are clearing the focus on an ancestor of the focused window, 590 // the ancestor will become the new focused window, so focus it 591 if (isAncestor) { 592 // Intentionally use a new actionId here because the above 593 // Blur() will clear the focus of the ancestors of focusedBC, and 594 // this Focus() call might need to update the focus of those ancestors, 595 // so it needs to have a newer actionId to make that happen. 596 Focus(window, nullptr, 0, true, false, false, true, 597 GenerateFocusActionId()); 598 } 599 } 600 } else { 601 window->SetFocusedElement(nullptr); 602 } 603 604 LOGFOCUS(("<<ClearFocus end>>")); 605 606 return NS_OK; 607 } 608 609 NS_IMETHODIMP 610 nsFocusManager::GetFocusedElementForWindow(mozIDOMWindowProxy* aWindow, 611 bool aDeep, 612 mozIDOMWindowProxy** aFocusedWindow, 613 Element** aElement) { 614 *aElement = nullptr; 615 if (aFocusedWindow) { 616 *aFocusedWindow = nullptr; 617 } 618 619 NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG); 620 nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow); 621 622 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow; 623 RefPtr<Element> focusedElement = 624 GetFocusedDescendant(window, 625 aDeep ? nsFocusManager::eIncludeAllDescendants 626 : nsFocusManager::eOnlyCurrentWindow, 627 getter_AddRefs(focusedWindow)); 628 629 focusedElement.forget(aElement); 630 631 if (aFocusedWindow) { 632 NS_IF_ADDREF(*aFocusedWindow = focusedWindow); 633 } 634 635 return NS_OK; 636 } 637 638 NS_IMETHODIMP 639 nsFocusManager::MoveCaretToFocus(mozIDOMWindowProxy* aWindow) { 640 nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(aWindow); 641 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav); 642 if (dsti) { 643 if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) { 644 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(dsti); 645 NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); 646 647 // don't move the caret for editable documents 648 bool isEditable; 649 docShell->GetEditable(&isEditable); 650 if (isEditable) { 651 return NS_OK; 652 } 653 654 RefPtr<PresShell> presShell = docShell->GetPresShell(); 655 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); 656 657 nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow); 658 if (RefPtr<Element> focusedElement = window->GetFocusedElement()) { 659 MoveCaretToFocus(presShell, focusedElement); 660 } 661 } 662 } 663 664 return NS_OK; 665 } 666 667 void nsFocusManager::WindowRaised(mozIDOMWindowProxy* aWindow, 668 uint64_t aActionId) { 669 if (!aWindow) { 670 return; 671 } 672 673 nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow); 674 BrowsingContext* bc = window->GetBrowsingContext(); 675 676 if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) { 677 LOGFOCUS(("Window %p Raised [Currently: %p %p] actionid: %" PRIu64, aWindow, 678 mActiveWindow.get(), mFocusedWindow.get(), aActionId)); 679 Document* doc = window->GetExtantDoc(); 680 if (doc && doc->GetDocumentURI()) { 681 LOGFOCUS((" Raised Window: %p %s", aWindow, 682 doc->GetDocumentURI()->GetSpecOrDefault().get())); 683 } 684 if (mActiveWindow) { 685 doc = mActiveWindow->GetExtantDoc(); 686 if (doc && doc->GetDocumentURI()) { 687 LOGFOCUS((" Active Window: %p %s", mActiveWindow.get(), 688 doc->GetDocumentURI()->GetSpecOrDefault().get())); 689 } 690 } 691 } 692 693 if (XRE_IsParentProcess()) { 694 if (mActiveWindow == window) { 695 // The window is already active, so there is no need to focus anything, 696 // but make sure that the right widget is focused. This is a special case 697 // for Windows because when restoring a minimized window, a second 698 // activation will occur and the top-level widget could be focused instead 699 // of the child we want. We solve this by calling SetFocus to ensure that 700 // what the focus manager thinks should be the current widget is actually 701 // focused. 702 EnsureCurrentWidgetFocused(CallerType::System); 703 return; 704 } 705 706 // lower the existing window, if any. This shouldn't happen usually. 707 if (nsCOMPtr<nsPIDOMWindowOuter> activeWindow = mActiveWindow) { 708 WindowLowered(activeWindow, aActionId); 709 } 710 } else if (bc->IsTop()) { 711 BrowsingContext* active = GetActiveBrowsingContext(); 712 if (active == bc && !mActiveBrowsingContextInContentSetFromOtherProcess) { 713 // EnsureCurrentWidgetFocused() should not be necessary with 714 // PuppetWidget. 715 return; 716 } 717 718 if (active && active != bc) { 719 if (active->IsInProcess()) { 720 nsCOMPtr<nsPIDOMWindowOuter> activeWindow = active->GetDOMWindow(); 721 WindowLowered(activeWindow, aActionId); 722 } 723 // No else, because trying to lower other-process windows 724 // from here can result in the BrowsingContext no longer 725 // existing in the parent process by the time it deserializes 726 // the IPC message. 727 } 728 } 729 730 nsCOMPtr<nsIDocShellTreeItem> docShellAsItem = window->GetDocShell(); 731 // If there's no docShellAsItem, this window must have been closed, 732 // in that case there is no tree owner. 733 if (!docShellAsItem) { 734 return; 735 } 736 737 // set this as the active window 738 if (XRE_IsParentProcess()) { 739 mActiveWindow = window; 740 } else if (bc->IsTop()) { 741 SetActiveBrowsingContextInContent(bc, aActionId, 742 false /* aIsEnteringBFCache */); 743 } 744 745 // ensure that the window is enabled and visible 746 nsCOMPtr<nsIDocShellTreeOwner> treeOwner; 747 docShellAsItem->GetTreeOwner(getter_AddRefs(treeOwner)); 748 if (nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner)) { 749 bool isEnabled = true; 750 if (NS_SUCCEEDED(baseWindow->GetEnabled(&isEnabled)) && !isEnabled) { 751 return; 752 } 753 754 baseWindow->SetVisibility(true); 755 } 756 757 if (XRE_IsParentProcess()) { 758 // Unsetting top-level focus upon lowering was inhibited to accommodate 759 // ATOK, so we need to do it here. 760 BrowserParent::UnsetTopLevelWebFocusAll(); 761 ActivateOrDeactivate(window, true); 762 } 763 764 // Retrieve the last focused element within the window that was raised. 765 MoveFocusToWindowAfterRaise(window, aActionId); 766 } 767 768 void nsFocusManager::MoveFocusToWindowAfterRaise(nsPIDOMWindowOuter* aWindow, 769 uint64_t aActionId) { 770 nsCOMPtr<nsPIDOMWindowOuter> currentWindow; 771 RefPtr<Element> currentFocus = GetFocusedDescendant( 772 aWindow, eIncludeAllDescendants, getter_AddRefs(currentWindow)); 773 774 NS_ASSERTION(currentWindow, "window raised with no window current"); 775 if (!currentWindow) { 776 return; 777 } 778 779 // We use mFocusedWindow here is basically for the case that iframe navigate 780 // from a.com to b.com for example, so it ends up being loaded in a different 781 // process after Fission, but 782 // currentWindow->GetBrowsingContext() == GetFocusedBrowsingContext() would 783 // still be true because focused browsing context is synced, and we won't 784 // fire a focus event while focusing if we use it as condition. 785 Focus(currentWindow, currentFocus, /* aFlags = */ 0, 786 /* aIsNewDocument = */ currentWindow != mFocusedWindow, 787 /* aFocusChanged = */ false, 788 /* aWindowRaised = */ true, /* aAdjustWidget = */ true, aActionId); 789 } 790 791 void nsFocusManager::WindowLowered(mozIDOMWindowProxy* aWindow, 792 uint64_t aActionId) { 793 if (!aWindow) { 794 return; 795 } 796 797 nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow); 798 799 if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) { 800 LOGFOCUS(("Window %p Lowered [Currently: %p %p]", aWindow, 801 mActiveWindow.get(), mFocusedWindow.get())); 802 Document* doc = window->GetExtantDoc(); 803 if (doc && doc->GetDocumentURI()) { 804 LOGFOCUS((" Lowered Window: %s", 805 doc->GetDocumentURI()->GetSpecOrDefault().get())); 806 } 807 if (mActiveWindow) { 808 doc = mActiveWindow->GetExtantDoc(); 809 if (doc && doc->GetDocumentURI()) { 810 LOGFOCUS((" Active Window: %s", 811 doc->GetDocumentURI()->GetSpecOrDefault().get())); 812 } 813 } 814 } 815 816 if (XRE_IsParentProcess()) { 817 if (mActiveWindow != window) { 818 return; 819 } 820 } else { 821 BrowsingContext* bc = window->GetBrowsingContext(); 822 BrowsingContext* active = GetActiveBrowsingContext(); 823 if (active != bc->Top()) { 824 return; 825 } 826 } 827 828 // clear the mouse capture as the active window has changed 829 PresShell::ReleaseCapturingContent(); 830 831 // In addition, reset the drag state to ensure that we are no longer in 832 // drag-select mode. 833 if (mFocusedWindow) { 834 nsCOMPtr<nsIDocShell> docShell = mFocusedWindow->GetDocShell(); 835 if (docShell) { 836 if (PresShell* presShell = docShell->GetPresShell()) { 837 RefPtr<nsFrameSelection> frameSelection = presShell->FrameSelection(); 838 frameSelection->SetDragState(false); 839 } 840 } 841 } 842 843 if (XRE_IsParentProcess()) { 844 ActivateOrDeactivate(window, false); 845 } 846 847 // keep track of the window being lowered, so that attempts to raise the 848 // window can be prevented until we return. Otherwise, focus can get into 849 // an unusual state. 850 mWindowBeingLowered = window; 851 if (XRE_IsParentProcess()) { 852 mActiveWindow = nullptr; 853 } else { 854 BrowsingContext* bc = window->GetBrowsingContext(); 855 if (bc == bc->Top()) { 856 SetActiveBrowsingContextInContent(nullptr, aActionId, 857 false /* aIsEnteringBFCache */); 858 } 859 } 860 861 if (mFocusedWindow) { 862 Blur(nullptr, nullptr, true, true, false, aActionId); 863 } 864 865 mWindowBeingLowered = nullptr; 866 } 867 868 void nsFocusManager::FocusedElementMayHaveMoved(nsIContent* aContent, 869 nsINode* aOldParent) { 870 if (!aOldParent) { 871 return; 872 } 873 874 if (aOldParent->IsElement() && 875 !aOldParent->AsElement()->State().HasState(ElementState::FOCUS_WITHIN)) { 876 return; 877 } 878 879 nsPIDOMWindowOuter* window = aContent->OwnerDoc()->GetWindow(); 880 if (!window) { 881 return; 882 } 883 884 Element* focusedElement = window->GetFocusedElement(); 885 if (!focusedElement) { 886 return; 887 } 888 889 if (!nsContentUtils::ContentIsHostIncludingDescendantOf(focusedElement, 890 aContent)) { 891 return; 892 } 893 if (aOldParent->IsElement()) { 894 // Clear the old ancestor chain. 895 NotifyFocusStateChange(aOldParent->AsElement(), nullptr, 0, false, false); 896 } 897 // XXX This is not very optimal. 898 // Clear the ancestor chain of focused element. 899 NotifyFocusStateChange(focusedElement, nullptr, 0, false, false); 900 // And set the correct states. 901 NotifyFocusStateChange(focusedElement, nullptr, 0, true, false); 902 } 903 904 void nsFocusManager::ContentInserted(nsIContent* aChild, 905 const ContentInsertInfo& aInfo) { 906 FocusedElementMayHaveMoved(aChild, aInfo.mOldParent); 907 } 908 909 void nsFocusManager::ContentAppended(nsIContent* aFirstNewContent, 910 const ContentAppendInfo& aInfo) { 911 FocusedElementMayHaveMoved(aFirstNewContent, aInfo.mOldParent); 912 } 913 914 static void UpdateFocusWithinState(Element* aElement, 915 nsIContent* aCommonAncestor, 916 bool aGettingFocus) { 917 Element* focusedElement = nullptr; 918 Document* document = aElement->GetComposedDoc(); 919 if (aElement && document) { 920 if (nsPIDOMWindowOuter* window = document->GetWindow()) { 921 focusedElement = window->GetFocusedElement(); 922 } 923 } 924 925 bool focusChanged = false; 926 for (nsIContent* content = aElement; content && content != aCommonAncestor; 927 content = content->GetFlattenedTreeParent()) { 928 Element* element = Element::FromNode(content); 929 if (!element) { 930 continue; 931 } 932 933 if (aGettingFocus) { 934 if (element->State().HasState(ElementState::FOCUS_WITHIN)) { 935 break; 936 } 937 938 element->AddStates(ElementState::FOCUS_WITHIN); 939 } else { 940 element->RemoveStates(ElementState::FOCUS_WITHIN); 941 } 942 943 focusChanged = focusChanged || element == focusedElement; 944 } 945 946 if (focusChanged && document->GetInnerWindow()) { 947 if (RefPtr<Navigation> navigation = 948 document->GetInnerWindow()->Navigation()) { 949 navigation->SetFocusedChangedDuringOngoingNavigation( 950 /* aFocusChangedDuringOngoingNavigation */ true); 951 } 952 } 953 } 954 955 static void MaybeFixUpFocusWithinState(Element* aElementToFocus, 956 Element* aFocusedElement) { 957 if (!aElementToFocus || aElementToFocus == aFocusedElement || 958 !aElementToFocus->IsInComposedDoc()) { 959 return; 960 } 961 // Focus was redirected, make sure the :focus-within state remains consistent. 962 auto* commonAncestor = [&]() -> nsIContent* { 963 if (!aFocusedElement || 964 aElementToFocus->OwnerDoc() != aFocusedElement->OwnerDoc()) { 965 return nullptr; 966 } 967 return nsContentUtils::GetCommonFlattenedTreeAncestor(aFocusedElement, 968 aElementToFocus); 969 }(); 970 UpdateFocusWithinState(aElementToFocus, commonAncestor, false); 971 } 972 973 nsresult nsFocusManager::ContentRemoved(Document* aDocument, 974 nsIContent* aContent, 975 const ContentRemoveInfo& aInfo) { 976 MOZ_ASSERT(aDocument); 977 MOZ_ASSERT(aContent); 978 979 if (aInfo.mNewParent) { 980 // Handled upon insertion in ContentAppended/Inserted. 981 return NS_OK; 982 } 983 984 nsPIDOMWindowOuter* windowPtr = aDocument->GetWindow(); 985 if (!windowPtr) { 986 return NS_OK; 987 } 988 989 Element* focusWithinElement = [&]() -> Element* { 990 if (auto* el = Element::FromNode(aContent)) { 991 return el; 992 } 993 if (auto* shadow = ShadowRoot::FromNode(aContent)) { 994 // Note that we only get here with ShadowRoots for shadow roots of form 995 // controls that we can un-attach. So if there's a focused element it must 996 // be inside our shadow tree already. 997 return shadow->Host(); 998 } 999 // Removing text / comments / etc can't affect the focus state. 1000 return nullptr; 1001 }(); 1002 1003 if (!focusWithinElement) { 1004 return NS_OK; 1005 } 1006 1007 const bool hasFocusWithinInThisDocument = 1008 focusWithinElement->State().HasAtLeastOneOfStates( 1009 ElementState::FOCUS | ElementState::FOCUS_WITHIN); 1010 1011 // if the content is currently focused in the window, or is an 1012 // shadow-including inclusive ancestor of the currently focused element, 1013 // reset the focus within that window. 1014 Element* previousFocusedElementPtr = windowPtr->GetFocusedElement(); 1015 if (!previousFocusedElementPtr) { 1016 if (hasFocusWithinInThisDocument) { 1017 // If we're in-between a blur and an incoming focus, we might have stale 1018 // :focus-within in our ancestor chain. Fix it up now. 1019 UpdateFocusWithinState(focusWithinElement, nullptr, false); 1020 } 1021 return NS_OK; 1022 } 1023 1024 if (previousFocusedElementPtr->State().HasState(ElementState::FOCUS)) { 1025 if (!hasFocusWithinInThisDocument) { 1026 // If the focused element has :focus, that means our ancestor should have 1027 // focus-within. 1028 return NS_OK; 1029 } 1030 } else if (!nsContentUtils::ContentIsFlattenedTreeDescendantOf( 1031 previousFocusedElementPtr, focusWithinElement)) { 1032 // Otherwise, previousFocusedElementPtr could be an <iframe>, we still need 1033 // to clear it in that case. 1034 return NS_OK; 1035 } 1036 1037 RefPtr previousFocusedElement = previousFocusedElementPtr; 1038 RefPtr window = windowPtr; 1039 RefPtr<Element> newFocusedElement = [&]() -> Element* { 1040 if (auto* sr = ShadowRoot::FromNode(aContent)) { 1041 if (sr->IsUAWidget() && sr->Host()->IsHTMLElement(nsGkAtoms::input)) { 1042 return sr->Host(); 1043 } 1044 } 1045 return nullptr; 1046 }(); 1047 1048 window->SetFocusedElement(newFocusedElement); 1049 1050 // if this window is currently focused, clear the global focused 1051 // element as well, but don't fire any events. 1052 if (window->GetBrowsingContext() == GetFocusedBrowsingContext()) { 1053 mFocusedElement = newFocusedElement; 1054 } else if (Document* subdoc = 1055 aDocument->GetSubDocumentFor(previousFocusedElement)) { 1056 // Check if the node that was focused is an iframe or similar by looking if 1057 // it has a subdocument. This would indicate that this focused iframe 1058 // and its descendants will be going away. We will need to move the focus 1059 // somewhere else, so just clear the focus in the toplevel window so that no 1060 // element is focused. 1061 // 1062 // The Fission case is handled in FlushAndCheckIfFocusable(). 1063 if (nsCOMPtr<nsIDocShell> docShell = subdoc->GetDocShell()) { 1064 nsCOMPtr<nsPIDOMWindowOuter> childWindow = docShell->GetWindow(); 1065 if (childWindow && 1066 IsSameOrAncestor(childWindow, GetFocusedBrowsingContext())) { 1067 if (XRE_IsParentProcess()) { 1068 nsCOMPtr<nsPIDOMWindowOuter> activeWindow = mActiveWindow; 1069 ClearFocus(activeWindow); 1070 } else { 1071 BrowsingContext* active = GetActiveBrowsingContext(); 1072 if (active) { 1073 if (active->IsInProcess()) { 1074 nsCOMPtr<nsPIDOMWindowOuter> activeWindow = 1075 active->GetDOMWindow(); 1076 ClearFocus(activeWindow); 1077 } else { 1078 mozilla::dom::ContentChild* contentChild = 1079 mozilla::dom::ContentChild::GetSingleton(); 1080 MOZ_ASSERT(contentChild); 1081 contentChild->SendClearFocus(active); 1082 } 1083 } // no else, because ClearFocus does nothing with nullptr 1084 } 1085 } 1086 } 1087 } 1088 1089 // Notify the editor in case we removed its ancestor limiter. 1090 if (previousFocusedElement->IsEditable()) { 1091 if (nsIDocShell* const docShell = aDocument->GetDocShell()) { 1092 if (HTMLEditor* const htmlEditor = docShell->GetHTMLEditor()) { 1093 Selection* const selection = htmlEditor->GetSelection(); 1094 if (selection && selection->GetFrameSelection() && 1095 previousFocusedElement == 1096 selection->GetFrameSelection()->GetAncestorLimiter()) { 1097 // The editing host may be being removed right now. So, it's already 1098 // removed from the child chain of the parent node, but it still know 1099 // the parent node. This could cause unexpected result at scheduling 1100 // paint of the caret. Therefore, we should call FinalizeSelection 1101 // after unblocking to run the script. 1102 nsContentUtils::AddScriptRunner( 1103 NewRunnableMethod("HTMLEditor::FinalizeSelection", htmlEditor, 1104 &HTMLEditor::FinalizeSelection)); 1105 } 1106 } 1107 } 1108 } 1109 1110 if (!newFocusedElement) { 1111 NotifyFocusStateChange(previousFocusedElement, nullptr, 0, 1112 /* aGettingFocus = */ false, false); 1113 } else { 1114 // We should already have the right state, which is managed by the <input> 1115 // widget. 1116 MOZ_ASSERT(newFocusedElement->State().HasState(ElementState::FOCUS)); 1117 } 1118 1119 // If we changed focused element and the element still has focus, let's 1120 // notify IME of focus. Note that if new focus move has already occurred 1121 // by running script, we should not let IMEStateManager of outdated focus 1122 // change. 1123 if (mFocusedElement == newFocusedElement && mFocusedWindow == window) { 1124 RefPtr<nsPresContext> presContext(aDocument->GetPresContext()); 1125 IMEStateManager::OnChangeFocus(presContext, newFocusedElement, 1126 InputContextAction::Cause::CAUSE_UNKNOWN); 1127 } 1128 1129 return NS_OK; 1130 } 1131 1132 void nsFocusManager::WindowShown(mozIDOMWindowProxy* aWindow, 1133 bool aNeedsFocus) { 1134 if (!aWindow) { 1135 return; 1136 } 1137 1138 nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow); 1139 1140 if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) { 1141 LOGFOCUS(("Window %p Shown [Currently: %p %p]", window.get(), 1142 mActiveWindow.get(), mFocusedWindow.get())); 1143 Document* doc = window->GetExtantDoc(); 1144 if (doc && doc->GetDocumentURI()) { 1145 LOGFOCUS(("Shown Window: %s", 1146 doc->GetDocumentURI()->GetSpecOrDefault().get())); 1147 } 1148 1149 if (mFocusedWindow) { 1150 doc = mFocusedWindow->GetExtantDoc(); 1151 if (doc && doc->GetDocumentURI()) { 1152 LOGFOCUS((" Focused Window: %s", 1153 doc->GetDocumentURI()->GetSpecOrDefault().get())); 1154 } 1155 } 1156 } 1157 1158 if (XRE_IsParentProcess()) { 1159 if (BrowsingContext* bc = window->GetBrowsingContext()) { 1160 if (bc->IsTop()) { 1161 bc->SetIsActiveBrowserWindow(bc->GetIsActiveBrowserWindow()); 1162 } 1163 } 1164 } 1165 1166 if (XRE_IsParentProcess()) { 1167 if (mFocusedWindow != window) { 1168 return; 1169 } 1170 } else { 1171 BrowsingContext* bc = window->GetBrowsingContext(); 1172 if (!bc || mFocusedBrowsingContextInContent != bc) { 1173 return; 1174 } 1175 // Sync the window for a newly-created OOP iframe 1176 // Set actionId to zero to signify that it should be ignored. 1177 SetFocusedWindowInternal(window, 0, false); 1178 } 1179 1180 if (aNeedsFocus) { 1181 nsCOMPtr<nsPIDOMWindowOuter> currentWindow; 1182 RefPtr<Element> currentFocus = GetFocusedDescendant( 1183 window, eIncludeAllDescendants, getter_AddRefs(currentWindow)); 1184 1185 if (currentWindow) { 1186 Focus(currentWindow, currentFocus, 0, true, false, false, true, 1187 GenerateFocusActionId()); 1188 } 1189 } else { 1190 // Sometimes, an element in a window can be focused before the window is 1191 // visible, which would mean that the widget may not be properly focused. 1192 // When the window becomes visible, make sure the right widget is focused. 1193 EnsureCurrentWidgetFocused(CallerType::System); 1194 } 1195 } 1196 1197 void nsFocusManager::WindowHidden(mozIDOMWindowProxy* aWindow, 1198 uint64_t aActionId, bool aIsEnteringBFCache) { 1199 // if there is no window or it is not the same or an ancestor of the 1200 // currently focused window, just return, as the current focus will not 1201 // be affected. 1202 1203 if (!aWindow) { 1204 return; 1205 } 1206 1207 nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow); 1208 1209 if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) { 1210 LOGFOCUS(("Window %p Hidden [Currently: %p %p] actionid: %" PRIu64, 1211 window.get(), mActiveWindow.get(), mFocusedWindow.get(), 1212 aActionId)); 1213 nsAutoCString spec; 1214 Document* doc = window->GetExtantDoc(); 1215 if (doc && doc->GetDocumentURI()) { 1216 LOGFOCUS((" Hide Window: %s", 1217 doc->GetDocumentURI()->GetSpecOrDefault().get())); 1218 } 1219 1220 if (mFocusedWindow) { 1221 doc = mFocusedWindow->GetExtantDoc(); 1222 if (doc && doc->GetDocumentURI()) { 1223 LOGFOCUS((" Focused Window: %s", 1224 doc->GetDocumentURI()->GetSpecOrDefault().get())); 1225 } 1226 } 1227 1228 if (mActiveWindow) { 1229 doc = mActiveWindow->GetExtantDoc(); 1230 if (doc && doc->GetDocumentURI()) { 1231 LOGFOCUS((" Active Window: %s", 1232 doc->GetDocumentURI()->GetSpecOrDefault().get())); 1233 } 1234 } 1235 } 1236 1237 if (!IsSameOrAncestor(window, mFocusedWindow)) { 1238 return; 1239 } 1240 1241 // at this point, we know that the window being hidden is either the focused 1242 // window, or an ancestor of the focused window. Either way, the focus is no 1243 // longer valid, so it needs to be updated. 1244 1245 const RefPtr<Element> oldFocusedElement = std::move(mFocusedElement); 1246 1247 nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell(); 1248 if (!focusedDocShell) { 1249 return; 1250 } 1251 1252 const RefPtr<PresShell> presShell = focusedDocShell->GetPresShell(); 1253 1254 if (oldFocusedElement && oldFocusedElement->IsInComposedDoc()) { 1255 NotifyFocusStateChange(oldFocusedElement, nullptr, 0, false, false); 1256 window->UpdateCommands(u"focus"_ns); 1257 1258 if (presShell) { 1259 RefPtr<Document> composedDoc = oldFocusedElement->GetComposedDoc(); 1260 SendFocusOrBlurEvent(eBlur, presShell, composedDoc, oldFocusedElement, 1261 false); 1262 } 1263 } 1264 1265 const RefPtr<nsPresContext> focusedPresContext = 1266 presShell ? presShell->GetPresContext() : nullptr; 1267 IMEStateManager::OnChangeFocus(focusedPresContext, nullptr, 1268 GetFocusMoveActionCause(0)); 1269 if (presShell) { 1270 SetCaretVisible(presShell, false, nullptr); 1271 } 1272 1273 // If a window is being "hidden" because its BrowsingContext is changing 1274 // remoteness, we don't want to handle docshell destruction by moving focus. 1275 // Instead, the focused browsing context should stay the way it is (so that 1276 // the newly "shown" window in the other process knows to take focus) and 1277 // we should just null out the process-local field. 1278 nsCOMPtr<nsIDocShell> docShellBeingHidden = window->GetDocShell(); 1279 // Check if we're currently hiding a non-remote nsDocShell due to its 1280 // BrowsingContext navigating to become remote. Normally, when a focused 1281 // subframe is hidden, focus is moved to the frame element, but focus should 1282 // stay with the BrowsingContext when performing a process switch. We don't 1283 // need to consider process switches where the hiding docshell is already 1284 // remote (ie. GetEmbedderElement is nullptr), as shifting remoteness to the 1285 // frame element is handled elsewhere. 1286 if (docShellBeingHidden && 1287 nsDocShell::Cast(docShellBeingHidden)->WillChangeProcess() && 1288 docShellBeingHidden->GetBrowsingContext()->GetEmbedderElement()) { 1289 if (mFocusedWindow != window) { 1290 // The window being hidden is an ancestor of the focused window. 1291 #ifdef DEBUG 1292 BrowsingContext* ancestor = window->GetBrowsingContext(); 1293 BrowsingContext* bc = mFocusedWindow->GetBrowsingContext(); 1294 for (;;) { 1295 if (!bc) { 1296 MOZ_ASSERT(false, "Should have found ancestor"); 1297 } 1298 bc = bc->GetParent(); 1299 if (ancestor == bc) { 1300 break; 1301 } 1302 } 1303 #endif 1304 // This call adjusts the focused browsing context and window. 1305 // The latter gets nulled out immediately below. 1306 SetFocusedWindowInternal(window, aActionId); 1307 } 1308 mFocusedWindow = nullptr; 1309 window->SetFocusedElement(nullptr); 1310 return; 1311 } 1312 1313 // if the docshell being hidden is being destroyed, then we want to move 1314 // focus somewhere else. Call ClearFocus on the toplevel window, which 1315 // will have the effect of clearing the focus and moving the focused window 1316 // to the toplevel window. But if the window isn't being destroyed, we are 1317 // likely just loading a new document in it, so we want to maintain the 1318 // focused window so that the new document gets properly focused. 1319 bool beingDestroyed = !docShellBeingHidden; 1320 if (docShellBeingHidden) { 1321 docShellBeingHidden->IsBeingDestroyed(&beingDestroyed); 1322 } 1323 if (beingDestroyed) { 1324 // There is usually no need to do anything if a toplevel window is going 1325 // away, as we assume that WindowLowered will be called. However, this may 1326 // not happen if nsIAppStartup::eForceQuit is used to quit, and can cause 1327 // a leak. So if the active window is being destroyed, call WindowLowered 1328 // directly. 1329 1330 if (XRE_IsParentProcess()) { 1331 nsCOMPtr<nsPIDOMWindowOuter> activeWindow = mActiveWindow; 1332 if (activeWindow == mFocusedWindow || activeWindow == window) { 1333 WindowLowered(activeWindow, aActionId); 1334 } else { 1335 ClearFocus(activeWindow); 1336 } 1337 } else { 1338 BrowsingContext* active = GetActiveBrowsingContext(); 1339 if (active) { 1340 if (nsCOMPtr<nsPIDOMWindowOuter> activeWindow = 1341 active->GetDOMWindow()) { 1342 if ((mFocusedWindow && 1343 mFocusedWindow->GetBrowsingContext() == active) || 1344 (window->GetBrowsingContext() == active)) { 1345 WindowLowered(activeWindow, aActionId); 1346 } else { 1347 ClearFocus(activeWindow); 1348 } 1349 } // else do nothing when an out-of-process iframe is torn down 1350 } 1351 } 1352 return; 1353 } 1354 1355 if (!XRE_IsParentProcess() && 1356 mActiveBrowsingContextInContent == 1357 docShellBeingHidden->GetBrowsingContext() && 1358 mActiveBrowsingContextInContent->GetIsInBFCache()) { 1359 SetActiveBrowsingContextInContent(nullptr, aActionId, aIsEnteringBFCache); 1360 } 1361 1362 // if the window being hidden is an ancestor of the focused window, adjust 1363 // the focused window so that it points to the one being hidden. This 1364 // ensures that the focused window isn't in a chain of frames that doesn't 1365 // exist any more. 1366 if (window != mFocusedWindow) { 1367 nsCOMPtr<nsIDocShellTreeItem> dsti = 1368 mFocusedWindow ? mFocusedWindow->GetDocShell() : nullptr; 1369 if (dsti) { 1370 nsCOMPtr<nsIDocShellTreeItem> parentDsti; 1371 dsti->GetInProcessParent(getter_AddRefs(parentDsti)); 1372 if (parentDsti) { 1373 if (nsCOMPtr<nsPIDOMWindowOuter> parentWindow = 1374 parentDsti->GetWindow()) { 1375 parentWindow->SetFocusedElement(nullptr); 1376 } 1377 } 1378 } 1379 1380 SetFocusedWindowInternal(window, aActionId); 1381 } 1382 } 1383 1384 void nsFocusManager::FireDelayedEvents(Document* aDocument) { 1385 MOZ_ASSERT(aDocument); 1386 1387 // fire any delayed focus and blur events in the same order that they were 1388 // added 1389 for (uint32_t i = 0; i < mDelayedBlurFocusEvents.Length(); i++) { 1390 if (mDelayedBlurFocusEvents[i].mDocument == aDocument) { 1391 if (!aDocument->GetInnerWindow() || 1392 !aDocument->GetInnerWindow()->IsCurrentInnerWindow()) { 1393 // If the document was navigated away from or is defunct, don't bother 1394 // firing events on it. Note the symmetry between this condition and 1395 // the similar one in Document.cpp:FireOrClearDelayedEvents. 1396 mDelayedBlurFocusEvents.RemoveElementAt(i); 1397 --i; 1398 } else if (!aDocument->EventHandlingSuppressed()) { 1399 EventMessage message = mDelayedBlurFocusEvents[i].mEventMessage; 1400 nsCOMPtr<EventTarget> target = mDelayedBlurFocusEvents[i].mTarget; 1401 RefPtr<PresShell> presShell = mDelayedBlurFocusEvents[i].mPresShell; 1402 nsCOMPtr<EventTarget> relatedTarget = 1403 mDelayedBlurFocusEvents[i].mRelatedTarget; 1404 mDelayedBlurFocusEvents.RemoveElementAt(i); 1405 1406 FireFocusOrBlurEvent(message, presShell, target, false, false, 1407 relatedTarget); 1408 --i; 1409 } 1410 } 1411 } 1412 } 1413 1414 void nsFocusManager::WasNuked(nsPIDOMWindowOuter* aWindow) { 1415 MOZ_ASSERT(aWindow, "Expected non-null window."); 1416 if (aWindow == mActiveWindow) { 1417 // TODO(emilio, bug 1933555): Figure out if we can assert below. 1418 // MOZ_ASSERT_UNREACHABLE("How come we're nuking a window that's still 1419 // active?"); 1420 mActiveWindow = nullptr; 1421 SetActiveBrowsingContextInChrome(nullptr, GenerateFocusActionId()); 1422 } 1423 if (aWindow == mFocusedWindow) { 1424 mFocusedWindow = nullptr; 1425 SetFocusedBrowsingContext(nullptr, GenerateFocusActionId()); 1426 mFocusedElement = nullptr; 1427 } 1428 } 1429 1430 nsFocusManager::BlurredElementInfo::BlurredElementInfo(Element& aElement) 1431 : mElement(aElement) {} 1432 1433 nsFocusManager::BlurredElementInfo::~BlurredElementInfo() = default; 1434 1435 // https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo 1436 static bool ShouldMatchFocusVisible(nsPIDOMWindowOuter* aWindow, 1437 const Element& aElement, 1438 int32_t aFocusFlags) { 1439 // If we were explicitly requested to show the ring, do it. 1440 if (aFocusFlags & nsIFocusManager::FLAG_SHOWRING) { 1441 return true; 1442 } 1443 1444 if (aFocusFlags & nsIFocusManager::FLAG_NOSHOWRING) { 1445 return false; 1446 } 1447 1448 if (aWindow->ShouldShowFocusRing()) { 1449 // The window decision also trumps any other heuristic. 1450 return true; 1451 } 1452 1453 // Any element which supports keyboard input (such as an input element, or any 1454 // other element which may trigger a virtual keyboard to be shown on focus if 1455 // a physical keyboard is not present) should always match :focus-visible when 1456 // focused. 1457 { 1458 if (aElement.IsHTMLElement(nsGkAtoms::textarea) || aElement.IsEditable()) { 1459 return true; 1460 } 1461 1462 if (auto* input = HTMLInputElement::FromNode(aElement)) { 1463 if (input->IsSingleLineTextControl()) { 1464 return true; 1465 } 1466 } 1467 } 1468 1469 switch (nsFocusManager::GetFocusMoveActionCause(aFocusFlags)) { 1470 case InputContextAction::CAUSE_KEY: 1471 // If the user interacts with the page via the keyboard, the currently 1472 // focused element should match :focus-visible (i.e. keyboard usage may 1473 // change whether this pseudo-class matches even if it doesn't affect 1474 // :focus). 1475 return true; 1476 case InputContextAction::CAUSE_UNKNOWN: 1477 // We render outlines if the last "known" focus method was by key or there 1478 // was no previous known focus method, otherwise we don't. 1479 return aWindow->UnknownFocusMethodShouldShowOutline(); 1480 case InputContextAction::CAUSE_MOUSE: 1481 case InputContextAction::CAUSE_TOUCH: 1482 case InputContextAction::CAUSE_LONGPRESS: 1483 // If the user interacts with the page via a pointing device, such that 1484 // the focus is moved to a new element which does not support user input, 1485 // the newly focused element should not match :focus-visible. 1486 return false; 1487 case InputContextAction::CAUSE_UNKNOWN_CHROME: 1488 case InputContextAction::CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT: 1489 case InputContextAction::CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT: 1490 // TODO(emilio): We could return some of these though, looking at 1491 // UserActivation. We may want to suppress focus rings for unknown / 1492 // programatic focus if the user is interacting with the page but not 1493 // during keyboard input, or such. 1494 MOZ_ASSERT_UNREACHABLE( 1495 "These don't get returned by GetFocusMoveActionCause"); 1496 break; 1497 } 1498 return false; 1499 } 1500 1501 /* static */ 1502 void nsFocusManager::NotifyFocusStateChange(Element* aElement, 1503 Element* aElementToFocus, 1504 int32_t aFlags, bool aGettingFocus, 1505 bool aShouldShowFocusRing) { 1506 MOZ_ASSERT_IF(aElementToFocus, !aGettingFocus); 1507 nsIContent* commonAncestor = nullptr; 1508 if (aElementToFocus) { 1509 commonAncestor = nsContentUtils::GetCommonFlattenedTreeAncestor( 1510 aElement, aElementToFocus); 1511 } 1512 1513 if (aGettingFocus) { 1514 ElementState stateToAdd = ElementState::FOCUS; 1515 if (aShouldShowFocusRing) { 1516 stateToAdd |= ElementState::FOCUSRING; 1517 } 1518 aElement->AddStates(stateToAdd); 1519 1520 for (nsIContent* host = aElement->GetContainingShadowHost(); host; 1521 host = host->GetContainingShadowHost()) { 1522 host->AsElement()->AddStates(ElementState::FOCUS); 1523 } 1524 } else { 1525 constexpr auto kStatesToRemove = 1526 ElementState::FOCUS | ElementState::FOCUSRING; 1527 aElement->RemoveStates(kStatesToRemove); 1528 for (nsIContent* host = aElement->GetContainingShadowHost(); host; 1529 host = host->GetContainingShadowHost()) { 1530 host->AsElement()->RemoveStates(kStatesToRemove); 1531 } 1532 } 1533 1534 // Special case for <input type="checkbox"> and <input type="radio">. 1535 // The other browsers cancel active state when they gets lost focus, but 1536 // does not do it for the other elements such as <button> and <a href="...">. 1537 // Additionally, they may be activated with <label>, but they will get focus 1538 // at `click`, but activated at `mousedown`. Therefore, we need to cancel 1539 // active state at moving focus. 1540 if (RefPtr<nsPresContext> presContext = 1541 aElement->GetPresContext(Element::PresContextFor::eForComposedDoc)) { 1542 RefPtr<EventStateManager> esm = presContext->EventStateManager(); 1543 auto* activeInputElement = 1544 HTMLInputElement::FromNodeOrNull(esm->GetActiveContent()); 1545 if (activeInputElement && 1546 (activeInputElement->ControlType() == FormControlType::InputCheckbox || 1547 activeInputElement->ControlType() == FormControlType::InputRadio) && 1548 !activeInputElement->State().HasState(ElementState::FOCUS)) { 1549 esm->SetContentState(nullptr, ElementState::ACTIVE); 1550 } 1551 } 1552 1553 UpdateFocusWithinState(aElement, commonAncestor, aGettingFocus); 1554 } 1555 1556 // static 1557 void nsFocusManager::EnsureCurrentWidgetFocused(CallerType aCallerType) { 1558 if (!mFocusedWindow || sTestMode) return; 1559 1560 // get the main child widget for the focused window and ensure that the 1561 // platform knows that this widget is focused. 1562 nsCOMPtr<nsIDocShell> docShell = mFocusedWindow->GetDocShell(); 1563 if (!docShell) { 1564 return; 1565 } 1566 RefPtr<PresShell> presShell = docShell->GetPresShell(); 1567 if (!presShell) { 1568 return; 1569 } 1570 nsCOMPtr<nsIWidget> widget = presShell->GetRootWidget(); 1571 if (!widget) { 1572 return; 1573 } 1574 widget->SetFocus(nsIWidget::Raise::No, aCallerType); 1575 } 1576 1577 void nsFocusManager::ActivateOrDeactivate(nsPIDOMWindowOuter* aWindow, 1578 bool aActive) { 1579 MOZ_ASSERT(XRE_IsParentProcess()); 1580 if (!aWindow) { 1581 return; 1582 } 1583 1584 if (BrowsingContext* bc = aWindow->GetBrowsingContext()) { 1585 MOZ_ASSERT(bc->IsTop()); 1586 1587 RefPtr<CanonicalBrowsingContext> chromeTop = 1588 bc->Canonical()->TopCrossChromeBoundary(); 1589 MOZ_ASSERT(bc == chromeTop); 1590 1591 chromeTop->SetIsActiveBrowserWindow(aActive); 1592 chromeTop->CallOnTopDescendants( 1593 [aActive](CanonicalBrowsingContext* aBrowsingContext) { 1594 aBrowsingContext->SetIsActiveBrowserWindow(aActive); 1595 return CallState::Continue; 1596 }, 1597 CanonicalBrowsingContext::TopDescendantKind::All); 1598 } 1599 1600 if (aWindow->GetExtantDoc()) { 1601 nsContentUtils::DispatchEventOnlyToChrome( 1602 aWindow->GetExtantDoc(), 1603 nsGlobalWindowInner::Cast(aWindow->GetCurrentInnerWindow()), 1604 aActive ? u"activate"_ns : u"deactivate"_ns, CanBubble::eYes, 1605 Cancelable::eYes, nullptr); 1606 } 1607 } 1608 1609 // Retrieves innerWindowId of the window of the last focused element to 1610 // log a warning to the website console. 1611 void LogWarningFullscreenWindowRaise(Element* aElement) { 1612 nsCOMPtr<nsFrameLoaderOwner> frameLoaderOwner(do_QueryInterface(aElement)); 1613 NS_ENSURE_TRUE_VOID(frameLoaderOwner); 1614 1615 RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader(); 1616 NS_ENSURE_TRUE_VOID(frameLoaderOwner); 1617 1618 RefPtr<BrowsingContext> browsingContext = frameLoader->GetBrowsingContext(); 1619 NS_ENSURE_TRUE_VOID(browsingContext); 1620 1621 WindowGlobalParent* windowGlobalParent = 1622 browsingContext->Canonical()->GetCurrentWindowGlobal(); 1623 NS_ENSURE_TRUE_VOID(windowGlobalParent); 1624 1625 // Log to console 1626 nsAutoString localizedMsg; 1627 nsTArray<nsString> params; 1628 nsresult rv = nsContentUtils::FormatLocalizedString( 1629 nsContentUtils::eDOM_PROPERTIES, "FullscreenExitWindowFocus", params, 1630 localizedMsg); 1631 1632 NS_ENSURE_SUCCESS_VOID(rv); 1633 1634 (void)nsContentUtils::ReportToConsoleByWindowID( 1635 localizedMsg, nsIScriptError::warningFlag, "DOM"_ns, 1636 windowGlobalParent->InnerWindowId(), 1637 SourceLocation(windowGlobalParent->GetDocumentURI())); 1638 } 1639 1640 // Ensure that when an embedded popup with a noautofocus attribute 1641 // like a date picker is opened and focused, the parent page does not blur 1642 static bool IsEmeddededInNoautofocusPopup(BrowsingContext& aBc) { 1643 auto* embedder = aBc.GetEmbedderElement(); 1644 if (!embedder) { 1645 return false; 1646 } 1647 nsIFrame* f = embedder->GetPrimaryFrame(); 1648 if (!f || !f->HasAnyStateBits(NS_FRAME_IN_POPUP)) { 1649 return false; 1650 } 1651 1652 nsIFrame* menuPopup = 1653 nsLayoutUtils::GetClosestFrameOfType(f, LayoutFrameType::MenuPopup); 1654 MOZ_ASSERT(menuPopup, "NS_FRAME_IN_POPUP lied?"); 1655 return static_cast<nsMenuPopupFrame*>(menuPopup) 1656 ->PopupElement() 1657 .GetXULBoolAttr(nsGkAtoms::noautofocus); 1658 } 1659 1660 Maybe<uint64_t> nsFocusManager::SetFocusInner(Element* aNewContent, 1661 int32_t aFlags, 1662 bool aFocusChanged, 1663 bool aAdjustWidget) { 1664 // if the element is not focusable, just return and leave the focus as is 1665 RefPtr<Element> elementToFocus = 1666 FlushAndCheckIfFocusable(aNewContent, aFlags); 1667 if (!elementToFocus) { 1668 return Nothing(); 1669 } 1670 1671 const RefPtr<BrowsingContext> focusedBrowsingContext = 1672 GetFocusedBrowsingContext(); 1673 1674 // check if the element to focus is a frame (iframe) containing a child 1675 // document. Frames are never directly focused; instead focusing a frame 1676 // means focus what is inside the frame. To do this, the descendant content 1677 // within the frame is retrieved and that will be focused instead. 1678 nsCOMPtr<nsPIDOMWindowOuter> newWindow; 1679 nsCOMPtr<nsPIDOMWindowOuter> subWindow = GetContentWindow(elementToFocus); 1680 if (subWindow) { 1681 elementToFocus = GetFocusedDescendant(subWindow, eIncludeAllDescendants, 1682 getter_AddRefs(newWindow)); 1683 1684 // since a window is being refocused, clear aFocusChanged so that the 1685 // caret position isn't updated. 1686 aFocusChanged = false; 1687 } 1688 1689 // unless it was set above, retrieve the window for the element to focus 1690 if (!newWindow) { 1691 newWindow = GetCurrentWindow(elementToFocus); 1692 } 1693 1694 RefPtr<BrowsingContext> newBrowsingContext; 1695 if (newWindow) { 1696 newBrowsingContext = newWindow->GetBrowsingContext(); 1697 } 1698 1699 // if the element is already focused, just return. Note that this happens 1700 // after the frame check above so that we compare the element that will be 1701 // focused rather than the frame it is in. 1702 if (!newWindow || (newBrowsingContext == GetFocusedBrowsingContext() && 1703 elementToFocus == mFocusedElement)) { 1704 return Nothing(); 1705 } 1706 1707 MOZ_ASSERT(newBrowsingContext); 1708 1709 BrowsingContext* browsingContextToFocus = newBrowsingContext; 1710 if (RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(elementToFocus)) { 1711 // Only look at pre-existing browsing contexts. If this function is 1712 // called during reflow, calling GetBrowsingContext() could cause frame 1713 // loader initialization at a time when it isn't safe. 1714 if (BrowsingContext* bc = flo->GetExtantBrowsingContext()) { 1715 // If focus is already in the subtree rooted at bc, return early 1716 // to match the single-process focus semantics. Otherwise, we'd 1717 // blur and immediately refocus whatever is focused. 1718 BrowsingContext* walk = focusedBrowsingContext; 1719 while (walk) { 1720 if (walk == bc) { 1721 return Nothing(); 1722 } 1723 walk = walk->GetParent(); 1724 } 1725 browsingContextToFocus = bc; 1726 } 1727 } 1728 1729 // don't allow focus to be placed in docshells or descendants of docshells 1730 // that are being destroyed. Also, ensure that the page hasn't been 1731 // unloaded. The prevents content from being refocused during an unload event. 1732 nsCOMPtr<nsIDocShell> newDocShell = newWindow->GetDocShell(); 1733 nsCOMPtr<nsIDocShell> docShell = newDocShell; 1734 while (docShell) { 1735 bool inUnload; 1736 docShell->GetIsInUnload(&inUnload); 1737 if (inUnload) { 1738 return Nothing(); 1739 } 1740 1741 bool beingDestroyed; 1742 docShell->IsBeingDestroyed(&beingDestroyed); 1743 if (beingDestroyed) { 1744 return Nothing(); 1745 } 1746 1747 BrowsingContext* bc = docShell->GetBrowsingContext(); 1748 1749 nsCOMPtr<nsIDocShellTreeItem> parentDsti; 1750 docShell->GetInProcessParent(getter_AddRefs(parentDsti)); 1751 docShell = do_QueryInterface(parentDsti); 1752 if (!docShell && !XRE_IsParentProcess()) { 1753 // We don't have an in-process parent, but let's see if we have 1754 // an in-process ancestor or if an out-of-process ancestor 1755 // is discarded. 1756 do { 1757 bc = bc->GetParent(); 1758 if (bc && bc->IsDiscarded()) { 1759 return Nothing(); 1760 } 1761 } while (bc && !bc->IsInProcess()); 1762 if (bc) { 1763 docShell = bc->GetDocShell(); 1764 } else { 1765 docShell = nullptr; 1766 } 1767 } 1768 } 1769 1770 bool focusMovesToDifferentBC = 1771 (focusedBrowsingContext != browsingContextToFocus); 1772 1773 if (focusedBrowsingContext && focusMovesToDifferentBC && 1774 nsContentUtils::IsHandlingKeyBoardEvent() && 1775 !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) { 1776 MOZ_ASSERT(browsingContextToFocus, 1777 "BrowsingContext to focus should be non-null."); 1778 1779 nsIPrincipal* focusedPrincipal = nullptr; 1780 nsIPrincipal* newPrincipal = nullptr; 1781 1782 if (XRE_IsParentProcess()) { 1783 if (WindowGlobalParent* focusedWindowGlobalParent = 1784 focusedBrowsingContext->Canonical()->GetCurrentWindowGlobal()) { 1785 focusedPrincipal = focusedWindowGlobalParent->DocumentPrincipal(); 1786 } 1787 1788 if (WindowGlobalParent* newWindowGlobalParent = 1789 browsingContextToFocus->Canonical()->GetCurrentWindowGlobal()) { 1790 newPrincipal = newWindowGlobalParent->DocumentPrincipal(); 1791 } 1792 } else if (focusedBrowsingContext->IsInProcess() && 1793 browsingContextToFocus->IsInProcess()) { 1794 nsCOMPtr<nsIScriptObjectPrincipal> focused = 1795 do_QueryInterface(focusedBrowsingContext->GetDOMWindow()); 1796 nsCOMPtr<nsIScriptObjectPrincipal> newFocus = 1797 do_QueryInterface(browsingContextToFocus->GetDOMWindow()); 1798 MOZ_ASSERT(focused && newFocus, 1799 "BrowsingContext should always have a window here."); 1800 focusedPrincipal = focused->GetPrincipal(); 1801 newPrincipal = newFocus->GetPrincipal(); 1802 } 1803 1804 if (!focusedPrincipal || !newPrincipal) { 1805 return Nothing(); 1806 } 1807 1808 if (!focusedPrincipal->Subsumes(newPrincipal)) { 1809 NS_WARNING("Not allowed to focus the new window!"); 1810 return Nothing(); 1811 } 1812 } 1813 1814 // to check if the new element is in the active window, compare the 1815 // new root docshell for the new element with the active window's docshell. 1816 RefPtr<BrowsingContext> newRootBrowsingContext = nullptr; 1817 bool isElementInActiveWindow = false; 1818 if (XRE_IsParentProcess()) { 1819 nsCOMPtr<nsPIDOMWindowOuter> newRootWindow = nullptr; 1820 nsCOMPtr<nsIDocShellTreeItem> dsti = newWindow->GetDocShell(); 1821 if (dsti) { 1822 nsCOMPtr<nsIDocShellTreeItem> root; 1823 dsti->GetInProcessRootTreeItem(getter_AddRefs(root)); 1824 newRootWindow = root ? root->GetWindow() : nullptr; 1825 1826 isElementInActiveWindow = 1827 (mActiveWindow && newRootWindow == mActiveWindow); 1828 } 1829 if (newRootWindow) { 1830 newRootBrowsingContext = newRootWindow->GetBrowsingContext(); 1831 } 1832 } else { 1833 // XXX This is wrong for `<iframe mozbrowser>` and for XUL 1834 // `<browser remote="true">`. See: 1835 // https://searchfox.org/mozilla-central/rev/8a63fc190b39ed6951abb4aef4a56487a43962bc/dom/base/nsFrameLoader.cpp#229-232 1836 newRootBrowsingContext = newBrowsingContext->Top(); 1837 // to check if the new element is in the active window, compare the 1838 // new root docshell for the new element with the active window's docshell. 1839 isElementInActiveWindow = 1840 (GetActiveBrowsingContext() == newRootBrowsingContext); 1841 } 1842 1843 // Exit fullscreen if a website focuses another window 1844 if (StaticPrefs::full_screen_api_exit_on_windowRaise() && 1845 !isElementInActiveWindow && (aFlags & FLAG_RAISE)) { 1846 if (XRE_IsParentProcess()) { 1847 if (Document* doc = mActiveWindow ? mActiveWindow->GetDoc() : nullptr) { 1848 Document::ClearPendingFullscreenRequests(doc); 1849 if (doc->GetFullscreenElement()) { 1850 LogWarningFullscreenWindowRaise(mFocusedElement); 1851 Document::AsyncExitFullscreen(doc); 1852 } 1853 } 1854 } else { 1855 BrowsingContext* activeBrowsingContext = GetActiveBrowsingContext(); 1856 if (activeBrowsingContext) { 1857 nsIDocShell* shell = activeBrowsingContext->GetDocShell(); 1858 if (shell) { 1859 if (Document* doc = shell->GetDocument()) { 1860 Document::ClearPendingFullscreenRequests(doc); 1861 if (doc->GetFullscreenElement()) { 1862 Document::AsyncExitFullscreen(doc); 1863 } 1864 } 1865 } else { 1866 mozilla::dom::ContentChild* contentChild = 1867 mozilla::dom::ContentChild::GetSingleton(); 1868 MOZ_ASSERT(contentChild); 1869 contentChild->SendMaybeExitFullscreen(activeBrowsingContext); 1870 } 1871 } 1872 } 1873 } 1874 1875 // if the FLAG_NOSWITCHFRAME flag is used, only allow the focus to be 1876 // shifted away from the current element if the new shell to focus is 1877 // the same or an ancestor shell of the currently focused shell. 1878 bool allowFrameSwitch = !(aFlags & FLAG_NOSWITCHFRAME) || 1879 IsSameOrAncestor(newWindow, focusedBrowsingContext); 1880 1881 // if the element is in the active window, frame switching is allowed and 1882 // the content is in a visible window, fire blur and focus events. 1883 bool sendFocusEvent = 1884 isElementInActiveWindow && allowFrameSwitch && IsWindowVisible(newWindow); 1885 1886 // Don't allow to steal the focus from chrome nodes if the caller cannot 1887 // access them. 1888 if (sendFocusEvent && mFocusedElement && 1889 mFocusedElement->OwnerDoc() != aNewContent->OwnerDoc() && 1890 mFocusedElement->NodePrincipal()->IsSystemPrincipal() && 1891 !nsContentUtils::LegacyIsCallerNativeCode() && 1892 !nsContentUtils::CanCallerAccess(mFocusedElement)) { 1893 sendFocusEvent = false; 1894 } 1895 1896 LOGCONTENT("Shift Focus: %s", elementToFocus.get()); 1897 LOGFOCUS((" Flags: %x Current Window: %p New Window: %p Current Element: %p", 1898 aFlags, mFocusedWindow.get(), newWindow.get(), 1899 mFocusedElement.get())); 1900 const uint64_t actionId = GenerateFocusActionId(); 1901 LOGFOCUS( 1902 (" In Active Window: %d Moves to different BrowsingContext: %d " 1903 "SendFocus: %d actionid: %" PRIu64, 1904 isElementInActiveWindow, focusMovesToDifferentBC, sendFocusEvent, 1905 actionId)); 1906 1907 if (sendFocusEvent) { 1908 Maybe<BlurredElementInfo> blurredInfo; 1909 if (mFocusedElement) { 1910 blurredInfo.emplace(*mFocusedElement); 1911 } 1912 // return if blurring fails or the focus changes during the blur 1913 if (focusedBrowsingContext) { 1914 // find the common ancestor of the currently focused window and the new 1915 // window. The ancestor will need to have its currently focused node 1916 // cleared once the document has been blurred. Otherwise, we'll be in a 1917 // state where a document is blurred yet the chain of windows above it 1918 // still points to that document. 1919 // For instance, in the following frame tree: 1920 // A 1921 // B C 1922 // D 1923 // D is focused and we want to focus C. Once D has been blurred, we need 1924 // to clear out the focus in A, otherwise A would still maintain that B 1925 // was focused, and B that D was focused. 1926 RefPtr<BrowsingContext> commonAncestor = 1927 focusMovesToDifferentBC 1928 ? GetCommonAncestor(newWindow, focusedBrowsingContext) 1929 : nullptr; 1930 1931 const bool needToClearFocusedElement = [&] { 1932 if (focusedBrowsingContext->IsChrome()) { 1933 // Always reset focused element if focus is currently in chrome 1934 // window, unless we're moving focus to a popup. 1935 return !IsEmeddededInNoautofocusPopup(*browsingContextToFocus); 1936 } 1937 if (focusedBrowsingContext->Top() != browsingContextToFocus->Top()) { 1938 // Only reset focused element if focus moves within the same top-level 1939 // content window. 1940 return false; 1941 } 1942 // XXX for the case that we try to focus an 1943 // already-focused-remote-frame, we would still send blur and focus 1944 // IPC to it, but they will not generate blur or focus event, we don't 1945 // want to reset activeElement on the remote frame. 1946 return focusMovesToDifferentBC || focusedBrowsingContext->IsInProcess(); 1947 }(); 1948 1949 const bool remainActive = 1950 focusMovesToDifferentBC && 1951 IsEmeddededInNoautofocusPopup(*browsingContextToFocus); 1952 1953 // TODO: MOZ_KnownLive is required due to bug 1770680 1954 if (!Blur(MOZ_KnownLive(needToClearFocusedElement 1955 ? focusedBrowsingContext.get() 1956 : nullptr), 1957 commonAncestor, focusMovesToDifferentBC, aAdjustWidget, 1958 remainActive, actionId, elementToFocus)) { 1959 MaybeFixUpFocusWithinState(elementToFocus, mFocusedElement); 1960 return Some(actionId); 1961 } 1962 } 1963 1964 Focus(newWindow, elementToFocus, aFlags, focusMovesToDifferentBC, 1965 aFocusChanged, false, aAdjustWidget, actionId, blurredInfo); 1966 } else { 1967 // otherwise, for inactive windows and when the caller cannot steal the 1968 // focus, update the node in the window, and raise the window if desired. 1969 if (allowFrameSwitch) { 1970 AdjustWindowFocus(newBrowsingContext, true, IsWindowVisible(newWindow), 1971 actionId, false /* aShouldClearAncestorFocus */, 1972 nullptr /* aAncestorBrowsingContextToFocus */); 1973 } 1974 1975 // set the focus node and method as needed 1976 uint32_t focusMethod = 1977 aFocusChanged ? aFlags & METHODANDRING_MASK 1978 : newWindow->GetFocusMethod() | 1979 (aFlags & (FLAG_SHOWRING | FLAG_NOSHOWRING)); 1980 newWindow->SetFocusedElement(elementToFocus, focusMethod); 1981 if (aFocusChanged) { 1982 if (nsCOMPtr<nsIDocShell> docShell = newWindow->GetDocShell()) { 1983 RefPtr<PresShell> presShell = docShell->GetPresShell(); 1984 if (presShell && presShell->DidInitialize()) { 1985 ScrollIntoView(presShell, elementToFocus, aFlags); 1986 } 1987 } 1988 } 1989 1990 // update the commands even when inactive so that the attributes for that 1991 // window are up to date. 1992 if (allowFrameSwitch) { 1993 newWindow->UpdateCommands(u"focus"_ns); 1994 } 1995 1996 if (aFlags & FLAG_RAISE) { 1997 if (newRootBrowsingContext) { 1998 if (XRE_IsParentProcess() || newRootBrowsingContext->IsInProcess()) { 1999 nsCOMPtr<nsPIDOMWindowOuter> outerWindow = 2000 newRootBrowsingContext->GetDOMWindow(); 2001 RaiseWindow(outerWindow, 2002 aFlags & FLAG_NONSYSTEMCALLER ? CallerType::NonSystem 2003 : CallerType::System, 2004 actionId); 2005 } else { 2006 mozilla::dom::ContentChild* contentChild = 2007 mozilla::dom::ContentChild::GetSingleton(); 2008 MOZ_ASSERT(contentChild); 2009 contentChild->SendRaiseWindow(newRootBrowsingContext, 2010 aFlags & FLAG_NONSYSTEMCALLER 2011 ? CallerType::NonSystem 2012 : CallerType::System, 2013 actionId); 2014 } 2015 } 2016 } 2017 } 2018 return Some(actionId); 2019 } 2020 2021 static BrowsingContext* GetParentIgnoreChromeBoundary(BrowsingContext* aBC) { 2022 // Chrome BrowsingContexts are only available in the parent process, so if 2023 // we're in a content process, we only worry about the context tree. 2024 if (XRE_IsParentProcess()) { 2025 return aBC->Canonical()->GetParentCrossChromeBoundary(); 2026 } 2027 return aBC->GetParent(); 2028 } 2029 2030 bool nsFocusManager::IsSameOrAncestor(BrowsingContext* aPossibleAncestor, 2031 BrowsingContext* aContext) const { 2032 if (!aPossibleAncestor) { 2033 return false; 2034 } 2035 2036 for (BrowsingContext* bc = aContext; bc; 2037 bc = GetParentIgnoreChromeBoundary(bc)) { 2038 if (bc == aPossibleAncestor) { 2039 return true; 2040 } 2041 } 2042 2043 return false; 2044 } 2045 2046 bool nsFocusManager::IsSameOrAncestor(nsPIDOMWindowOuter* aPossibleAncestor, 2047 nsPIDOMWindowOuter* aWindow) const { 2048 if (aWindow && aPossibleAncestor) { 2049 return IsSameOrAncestor(aPossibleAncestor->GetBrowsingContext(), 2050 aWindow->GetBrowsingContext()); 2051 } 2052 return false; 2053 } 2054 2055 bool nsFocusManager::IsSameOrAncestor(nsPIDOMWindowOuter* aPossibleAncestor, 2056 BrowsingContext* aContext) const { 2057 if (aPossibleAncestor) { 2058 return IsSameOrAncestor(aPossibleAncestor->GetBrowsingContext(), aContext); 2059 } 2060 return false; 2061 } 2062 2063 bool nsFocusManager::IsSameOrAncestor(BrowsingContext* aPossibleAncestor, 2064 nsPIDOMWindowOuter* aWindow) const { 2065 if (aWindow) { 2066 return IsSameOrAncestor(aPossibleAncestor, aWindow->GetBrowsingContext()); 2067 } 2068 return false; 2069 } 2070 2071 mozilla::dom::BrowsingContext* nsFocusManager::GetCommonAncestor( 2072 nsPIDOMWindowOuter* aWindow, mozilla::dom::BrowsingContext* aContext) { 2073 NS_ENSURE_TRUE(aWindow && aContext, nullptr); 2074 2075 if (XRE_IsParentProcess()) { 2076 nsCOMPtr<nsIDocShellTreeItem> dsti1 = aWindow->GetDocShell(); 2077 NS_ENSURE_TRUE(dsti1, nullptr); 2078 2079 nsCOMPtr<nsIDocShellTreeItem> dsti2 = aContext->GetDocShell(); 2080 NS_ENSURE_TRUE(dsti2, nullptr); 2081 2082 AutoTArray<nsIDocShellTreeItem*, 30> parents1, parents2; 2083 do { 2084 parents1.AppendElement(dsti1); 2085 nsCOMPtr<nsIDocShellTreeItem> parentDsti1; 2086 dsti1->GetInProcessParent(getter_AddRefs(parentDsti1)); 2087 dsti1.swap(parentDsti1); 2088 } while (dsti1); 2089 do { 2090 parents2.AppendElement(dsti2); 2091 nsCOMPtr<nsIDocShellTreeItem> parentDsti2; 2092 dsti2->GetInProcessParent(getter_AddRefs(parentDsti2)); 2093 dsti2.swap(parentDsti2); 2094 } while (dsti2); 2095 2096 uint32_t pos1 = parents1.Length(); 2097 uint32_t pos2 = parents2.Length(); 2098 nsIDocShellTreeItem* parent = nullptr; 2099 uint32_t len; 2100 for (len = std::min(pos1, pos2); len > 0; --len) { 2101 nsIDocShellTreeItem* child1 = parents1.ElementAt(--pos1); 2102 nsIDocShellTreeItem* child2 = parents2.ElementAt(--pos2); 2103 if (child1 != child2) { 2104 break; 2105 } 2106 parent = child1; 2107 } 2108 2109 return parent ? parent->GetBrowsingContext() : nullptr; 2110 } 2111 2112 BrowsingContext* bc1 = aWindow->GetBrowsingContext(); 2113 NS_ENSURE_TRUE(bc1, nullptr); 2114 2115 BrowsingContext* bc2 = aContext; 2116 2117 AutoTArray<BrowsingContext*, 30> parents1, parents2; 2118 do { 2119 parents1.AppendElement(bc1); 2120 bc1 = bc1->GetParent(); 2121 } while (bc1); 2122 do { 2123 parents2.AppendElement(bc2); 2124 bc2 = bc2->GetParent(); 2125 } while (bc2); 2126 2127 uint32_t pos1 = parents1.Length(); 2128 uint32_t pos2 = parents2.Length(); 2129 BrowsingContext* parent = nullptr; 2130 uint32_t len; 2131 for (len = std::min(pos1, pos2); len > 0; --len) { 2132 BrowsingContext* child1 = parents1.ElementAt(--pos1); 2133 BrowsingContext* child2 = parents2.ElementAt(--pos2); 2134 if (child1 != child2) { 2135 break; 2136 } 2137 parent = child1; 2138 } 2139 2140 return parent; 2141 } 2142 2143 bool nsFocusManager::AdjustInProcessWindowFocus( 2144 BrowsingContext* aBrowsingContext, bool aCheckPermission, bool aIsVisible, 2145 uint64_t aActionId, bool aShouldClearAncestorFocus, 2146 BrowsingContext* aAncestorBrowsingContextToFocus) { 2147 MOZ_ASSERT_IF(aAncestorBrowsingContextToFocus, aShouldClearAncestorFocus); 2148 if (ActionIdComparableAndLower(aActionId, 2149 mActionIdForFocusedBrowsingContextInContent)) { 2150 LOGFOCUS( 2151 ("Ignored an attempt to adjust an in-process BrowsingContext [%p] as " 2152 "focused from another process due to stale action id %" PRIu64 ".", 2153 aBrowsingContext, aActionId)); 2154 return false; 2155 } 2156 2157 BrowsingContext* bc = aBrowsingContext; 2158 bool needToNotifyOtherProcess = false; 2159 while (bc) { 2160 // get the containing <iframe> or equivalent element so that it can be 2161 // focused below. 2162 nsCOMPtr<Element> frameElement = bc->GetEmbedderElement(); 2163 BrowsingContext* parent = bc->GetParent(); 2164 if (!parent && XRE_IsParentProcess()) { 2165 CanonicalBrowsingContext* canonical = bc->Canonical(); 2166 RefPtr<WindowGlobalParent> embedder = 2167 canonical->GetEmbedderWindowGlobal(); 2168 if (embedder) { 2169 parent = embedder->BrowsingContext(); 2170 } 2171 } 2172 bc = parent; 2173 if (!bc) { 2174 break; 2175 } 2176 if (!frameElement && XRE_IsContentProcess()) { 2177 needToNotifyOtherProcess = true; 2178 continue; 2179 } 2180 2181 nsCOMPtr<nsPIDOMWindowOuter> window = bc->GetDOMWindow(); 2182 MOZ_ASSERT(window); 2183 // if the parent window is visible but the original window was not, then we 2184 // have likely moved up and out from a hidden tab to the browser window, or 2185 // a similar such arrangement. Stop adjusting the current nodes. 2186 if (IsWindowVisible(window) != aIsVisible) { 2187 break; 2188 } 2189 2190 // When aCheckPermission is true, we should check whether the caller can 2191 // access the window or not. If it cannot access, we should stop the 2192 // adjusting. 2193 if (aCheckPermission && !nsContentUtils::LegacyIsCallerNativeCode() && 2194 !nsContentUtils::CanCallerAccess(window->GetCurrentInnerWindow())) { 2195 break; 2196 } 2197 2198 if (aShouldClearAncestorFocus) { 2199 // This is the BrowsingContext that receives the focus, no need to clear 2200 // its focused element and the rest of the ancestors. 2201 if (window->GetBrowsingContext() == aAncestorBrowsingContextToFocus) { 2202 break; 2203 } 2204 2205 window->SetFocusedElement(nullptr); 2206 continue; 2207 } 2208 2209 if (frameElement != window->GetFocusedElement()) { 2210 window->SetFocusedElement(frameElement); 2211 2212 RefPtr<nsFrameLoaderOwner> loaderOwner = do_QueryObject(frameElement); 2213 MOZ_ASSERT(loaderOwner); 2214 RefPtr<nsFrameLoader> loader = loaderOwner->GetFrameLoader(); 2215 if (loader && loader->IsRemoteFrame() && 2216 GetFocusedBrowsingContext() == bc) { 2217 Blur(nullptr, nullptr, true, true, false, aActionId); 2218 } 2219 } 2220 } 2221 return needToNotifyOtherProcess; 2222 } 2223 2224 void nsFocusManager::AdjustWindowFocus( 2225 BrowsingContext* aBrowsingContext, bool aCheckPermission, bool aIsVisible, 2226 uint64_t aActionId, bool aShouldClearAncestorFocus, 2227 BrowsingContext* aAncestorBrowsingContextToFocus) { 2228 MOZ_ASSERT_IF(aAncestorBrowsingContextToFocus, aShouldClearAncestorFocus); 2229 if (AdjustInProcessWindowFocus(aBrowsingContext, aCheckPermission, aIsVisible, 2230 aActionId, aShouldClearAncestorFocus, 2231 aAncestorBrowsingContextToFocus)) { 2232 // Some ancestors of aBrowsingContext isn't in this process, so notify other 2233 // processes to adjust their focused element. 2234 mozilla::dom::ContentChild* contentChild = 2235 mozilla::dom::ContentChild::GetSingleton(); 2236 MOZ_ASSERT(contentChild); 2237 contentChild->SendAdjustWindowFocus(aBrowsingContext, aIsVisible, aActionId, 2238 aShouldClearAncestorFocus, 2239 aAncestorBrowsingContextToFocus); 2240 } 2241 } 2242 2243 bool nsFocusManager::IsWindowVisible(nsPIDOMWindowOuter* aWindow) { 2244 if (!aWindow || nsGlobalWindowOuter::Cast(aWindow)->IsFrozen()) { 2245 return false; 2246 } 2247 2248 // Check if the inner window is frozen as well. This can happen when a focus 2249 // change occurs while restoring a previous page. 2250 auto* innerWindow = 2251 nsGlobalWindowInner::Cast(aWindow->GetCurrentInnerWindow()); 2252 if (!innerWindow || innerWindow->IsFrozen()) { 2253 return false; 2254 } 2255 2256 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell(); 2257 nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(docShell)); 2258 if (!baseWin) { 2259 return false; 2260 } 2261 2262 bool visible = false; 2263 baseWin->GetVisibility(&visible); 2264 return visible; 2265 } 2266 2267 bool nsFocusManager::IsNonFocusableRoot(nsIContent* aContent) { 2268 MOZ_ASSERT(aContent, "aContent must not be NULL"); 2269 MOZ_ASSERT(aContent->IsInComposedDoc(), "aContent must be in a document"); 2270 2271 // If the uncomposed document of aContent is in designMode, the root element 2272 // is not focusable. 2273 // NOTE: Most elements whose uncomposed document is in design mode are not 2274 // focusable, just the document is focusable. However, if it's in a 2275 // shadow tree, it may be focus able even if the shadow host is in 2276 // design mode. 2277 // Also, if aContent is not editable and it's not in designMode, it's not 2278 // focusable. 2279 // And in userfocusignored context nothing is focusable. 2280 Document* doc = aContent->GetComposedDoc(); 2281 NS_ASSERTION(doc, "aContent must have current document"); 2282 return aContent == doc->GetRootElement() && 2283 (aContent->IsInDesignMode() || !aContent->IsEditable()); 2284 } 2285 2286 Element* nsFocusManager::FlushAndCheckIfFocusable(Element* aElement, 2287 uint32_t aFlags) { 2288 if (!aElement) { 2289 return nullptr; 2290 } 2291 2292 nsCOMPtr<Document> doc = aElement->GetComposedDoc(); 2293 // can't focus elements that are not in documents 2294 if (!doc) { 2295 LOGCONTENT("Cannot focus %s because content not in document", aElement) 2296 return nullptr; 2297 } 2298 2299 // Make sure that our frames are up to date while ensuring the presshell is 2300 // also initialized in case we come from a script calling focus() early. 2301 mEventHandlingNeedsFlush = false; 2302 doc->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames); 2303 2304 return GetTheFocusableArea(aElement, aFlags); 2305 } 2306 2307 bool nsFocusManager::Blur(BrowsingContext* aBrowsingContextToClear, 2308 BrowsingContext* aAncestorBrowsingContextToFocus, 2309 bool aIsLeavingDocument, bool aAdjustWidget, 2310 bool aRemainActive, uint64_t aActionId, 2311 Element* aElementToFocus) { 2312 if (XRE_IsParentProcess()) { 2313 return BlurImpl(aBrowsingContextToClear, aAncestorBrowsingContextToFocus, 2314 aIsLeavingDocument, aAdjustWidget, aRemainActive, 2315 aElementToFocus, aActionId); 2316 } 2317 mozilla::dom::ContentChild* contentChild = 2318 mozilla::dom::ContentChild::GetSingleton(); 2319 MOZ_ASSERT(contentChild); 2320 bool windowToClearHandled = false; 2321 bool ancestorWindowToFocusHandled = false; 2322 2323 RefPtr<BrowsingContext> focusedBrowsingContext = GetFocusedBrowsingContext(); 2324 if (focusedBrowsingContext && focusedBrowsingContext->IsDiscarded()) { 2325 focusedBrowsingContext = nullptr; 2326 } 2327 if (!focusedBrowsingContext) { 2328 mFocusedElement = nullptr; 2329 return true; 2330 } 2331 if (aBrowsingContextToClear && aBrowsingContextToClear->IsDiscarded()) { 2332 aBrowsingContextToClear = nullptr; 2333 } 2334 if (aAncestorBrowsingContextToFocus && 2335 aAncestorBrowsingContextToFocus->IsDiscarded()) { 2336 aAncestorBrowsingContextToFocus = nullptr; 2337 } 2338 // XXX should more early returns from BlurImpl be hoisted here to avoid 2339 // processing aBrowsingContextToClear and aAncestorBrowsingContextToFocus in 2340 // other processes when BlurImpl returns early in this process? Or should the 2341 // IPC messages for those be sent by BlurImpl itself, in which case they could 2342 // arrive late? 2343 if (focusedBrowsingContext->IsInProcess()) { 2344 if (aBrowsingContextToClear && !aBrowsingContextToClear->IsInProcess()) { 2345 MOZ_RELEASE_ASSERT(!(aAncestorBrowsingContextToFocus && 2346 !aAncestorBrowsingContextToFocus->IsInProcess()), 2347 "Both aBrowsingContextToClear and " 2348 "aAncestorBrowsingContextToFocus are " 2349 "out-of-process."); 2350 contentChild->SendSetFocusedElement(aBrowsingContextToClear, false); 2351 } 2352 if (aAncestorBrowsingContextToFocus && 2353 !aAncestorBrowsingContextToFocus->IsInProcess()) { 2354 contentChild->SendSetFocusedElement(aAncestorBrowsingContextToFocus, 2355 true); 2356 } 2357 return BlurImpl(aBrowsingContextToClear, aAncestorBrowsingContextToFocus, 2358 aIsLeavingDocument, aAdjustWidget, aRemainActive, 2359 aElementToFocus, aActionId); 2360 } 2361 if (aBrowsingContextToClear && aBrowsingContextToClear->IsInProcess()) { 2362 nsPIDOMWindowOuter* windowToClear = aBrowsingContextToClear->GetDOMWindow(); 2363 MOZ_ASSERT(windowToClear); 2364 windowToClear->SetFocusedElement(nullptr); 2365 windowToClearHandled = true; 2366 } 2367 if (aAncestorBrowsingContextToFocus && 2368 aAncestorBrowsingContextToFocus->IsInProcess()) { 2369 nsPIDOMWindowOuter* ancestorWindowToFocus = 2370 aAncestorBrowsingContextToFocus->GetDOMWindow(); 2371 MOZ_ASSERT(ancestorWindowToFocus); 2372 ancestorWindowToFocus->SetFocusedElement(nullptr, 0, true); 2373 ancestorWindowToFocusHandled = true; 2374 } 2375 // The expectation is that the blurring would eventually result in an IPC 2376 // message doing this anyway, but this doesn't happen if the focus is in OOP 2377 // iframe which won't try to bounce an IPC message to its parent frame. 2378 SetFocusedWindowInternal(nullptr, aActionId); 2379 contentChild->SendBlurToParent( 2380 focusedBrowsingContext, aBrowsingContextToClear, 2381 aAncestorBrowsingContextToFocus, aIsLeavingDocument, aAdjustWidget, 2382 windowToClearHandled, ancestorWindowToFocusHandled, aActionId); 2383 return true; 2384 } 2385 2386 void nsFocusManager::BlurFromOtherProcess( 2387 mozilla::dom::BrowsingContext* aFocusedBrowsingContext, 2388 mozilla::dom::BrowsingContext* aBrowsingContextToClear, 2389 mozilla::dom::BrowsingContext* aAncestorBrowsingContextToFocus, 2390 bool aIsLeavingDocument, bool aAdjustWidget, uint64_t aActionId) { 2391 if (aFocusedBrowsingContext != GetFocusedBrowsingContext()) { 2392 return; 2393 } 2394 BlurImpl(aBrowsingContextToClear, aAncestorBrowsingContextToFocus, 2395 aIsLeavingDocument, aAdjustWidget, /* aRemainActive = */ false, 2396 nullptr, aActionId); 2397 } 2398 2399 bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear, 2400 BrowsingContext* aAncestorBrowsingContextToFocus, 2401 bool aIsLeavingDocument, bool aAdjustWidget, 2402 bool aRemainActive, Element* aElementToFocus, 2403 uint64_t aActionId) { 2404 LOGFOCUS(("<<Blur begin actionid: %" PRIu64 ">>", aActionId)); 2405 2406 // hold a reference to the focused content, which may be null 2407 RefPtr<Element> element = mFocusedElement; 2408 if (element) { 2409 if (!element->IsInComposedDoc()) { 2410 mFocusedElement = nullptr; 2411 return true; 2412 } 2413 } 2414 2415 RefPtr<BrowsingContext> focusedBrowsingContext = GetFocusedBrowsingContext(); 2416 // hold a reference to the focused window 2417 nsCOMPtr<nsPIDOMWindowOuter> window; 2418 if (focusedBrowsingContext) { 2419 window = focusedBrowsingContext->GetDOMWindow(); 2420 } 2421 if (!window) { 2422 mFocusedElement = nullptr; 2423 return true; 2424 } 2425 2426 nsCOMPtr<nsIDocShell> docShell = window->GetDocShell(); 2427 if (!docShell) { 2428 if (XRE_IsContentProcess() && 2429 ActionIdComparableAndLower( 2430 aActionId, mActionIdForFocusedBrowsingContextInContent)) { 2431 // Unclear if this ever happens. 2432 LOGFOCUS( 2433 ("Ignored an attempt to null out focused BrowsingContext when " 2434 "docShell is null due to a stale action id %" PRIu64 ".", 2435 aActionId)); 2436 return true; 2437 } 2438 2439 mFocusedWindow = nullptr; 2440 // Setting focused BrowsingContext to nullptr to avoid leaking in print 2441 // preview. 2442 SetFocusedBrowsingContext(nullptr, aActionId); 2443 mFocusedElement = nullptr; 2444 return true; 2445 } 2446 2447 // Keep a ref to presShell since dispatching the DOM event may cause 2448 // the document to be destroyed. 2449 RefPtr<PresShell> presShell = docShell->GetPresShell(); 2450 if (!presShell) { 2451 if (XRE_IsContentProcess() && 2452 ActionIdComparableAndLower( 2453 aActionId, mActionIdForFocusedBrowsingContextInContent)) { 2454 // Unclear if this ever happens. 2455 LOGFOCUS( 2456 ("Ignored an attempt to null out focused BrowsingContext when " 2457 "presShell is null due to a stale action id %" PRIu64 ".", 2458 aActionId)); 2459 return true; 2460 } 2461 mFocusedElement = nullptr; 2462 mFocusedWindow = nullptr; 2463 // Setting focused BrowsingContext to nullptr to avoid leaking in print 2464 // preview. 2465 SetFocusedBrowsingContext(nullptr, aActionId); 2466 return true; 2467 } 2468 2469 const RefPtr<nsPresContext> focusedPresContext = 2470 GetActiveBrowsingContext() ? presShell->GetPresContext() : nullptr; 2471 IMEStateManager::OnChangeFocus(focusedPresContext, nullptr, 2472 GetFocusMoveActionCause(0)); 2473 2474 // now adjust the actual focus, by clearing the fields in the focus manager 2475 // and in the window. 2476 mFocusedElement = nullptr; 2477 if (aBrowsingContextToClear) { 2478 nsPIDOMWindowOuter* windowToClear = aBrowsingContextToClear->GetDOMWindow(); 2479 if (windowToClear) { 2480 windowToClear->SetFocusedElement(nullptr); 2481 } 2482 } 2483 2484 LOGCONTENT("Element %s has been blurred", element.get()); 2485 2486 // Don't fire blur event on the root content which isn't editable. 2487 bool sendBlurEvent = 2488 element && element->IsInComposedDoc() && !IsNonFocusableRoot(element); 2489 if (element) { 2490 if (sendBlurEvent) { 2491 NotifyFocusStateChange(element, aElementToFocus, 0, false, false); 2492 } 2493 2494 if (!aRemainActive) { 2495 bool windowBeingLowered = !aBrowsingContextToClear && 2496 !aAncestorBrowsingContextToFocus && 2497 aIsLeavingDocument && aAdjustWidget; 2498 // If the object being blurred is a remote browser, deactivate remote 2499 // content 2500 if (BrowserParent* remote = BrowserParent::GetFrom(element)) { 2501 MOZ_ASSERT(XRE_IsParentProcess()); 2502 // Let's deactivate all remote browsers. 2503 BrowsingContext* topLevelBrowsingContext = remote->GetBrowsingContext(); 2504 topLevelBrowsingContext->PreOrderWalk([&](BrowsingContext* aContext) { 2505 if (WindowGlobalParent* windowGlobalParent = 2506 aContext->Canonical()->GetCurrentWindowGlobal()) { 2507 if (RefPtr<BrowserParent> browserParent = 2508 windowGlobalParent->GetBrowserParent()) { 2509 browserParent->Deactivate(windowBeingLowered, aActionId); 2510 LOGFOCUS( 2511 ("%s remote browser deactivated %p, %d, actionid: %" PRIu64, 2512 aContext == topLevelBrowsingContext ? "Top-level" 2513 : "OOP iframe", 2514 browserParent.get(), windowBeingLowered, aActionId)); 2515 } 2516 } 2517 }); 2518 } 2519 2520 // Same as above but for out-of-process iframes 2521 if (BrowserBridgeChild* bbc = BrowserBridgeChild::GetFrom(element)) { 2522 bbc->Deactivate(windowBeingLowered, aActionId); 2523 LOGFOCUS( 2524 ("Out-of-process iframe deactivated %p, %d, actionid: %" PRIu64, 2525 bbc, windowBeingLowered, aActionId)); 2526 } 2527 } 2528 } 2529 2530 bool result = true; 2531 if (sendBlurEvent) { 2532 // if there is an active window, update commands. If there isn't an active 2533 // window, then this was a blur caused by the active window being lowered, 2534 // so there is no need to update the commands 2535 if (GetActiveBrowsingContext()) { 2536 window->UpdateCommands(u"focus"_ns); 2537 } 2538 2539 SendFocusOrBlurEvent(eBlur, presShell, element->GetComposedDoc(), element, 2540 false, false, aElementToFocus); 2541 } 2542 2543 // if we are leaving the document or the window was lowered, make the caret 2544 // invisible. 2545 if (aIsLeavingDocument || !GetActiveBrowsingContext()) { 2546 SetCaretVisible(presShell, false, nullptr); 2547 } 2548 2549 RefPtr<AccessibleCaretEventHub> eventHub = 2550 presShell->GetAccessibleCaretEventHub(); 2551 if (eventHub) { 2552 eventHub->NotifyBlur(aIsLeavingDocument || !GetActiveBrowsingContext()); 2553 } 2554 2555 // at this point, it is expected that this window will be still be 2556 // focused, but the focused element will be null, as it was cleared before 2557 // the event. If this isn't the case, then something else was focused during 2558 // the blur event above and we should just return. However, if 2559 // aIsLeavingDocument is set, a new document is desired, so make sure to 2560 // blur the document and window. 2561 if (GetFocusedBrowsingContext() != window->GetBrowsingContext() || 2562 (mFocusedElement != nullptr && !aIsLeavingDocument)) { 2563 result = false; 2564 } else if (aIsLeavingDocument) { 2565 window->TakeFocus(false, 0); 2566 2567 // clear the focus so that the ancestor frame hierarchy is in the correct 2568 // state. Pass true because aAncestorBrowsingContextToFocus is thought to be 2569 // focused at this point. 2570 if (aAncestorBrowsingContextToFocus) { 2571 nsPIDOMWindowOuter* ancestorWindowToFocus = 2572 aAncestorBrowsingContextToFocus->GetDOMWindow(); 2573 if (ancestorWindowToFocus) { 2574 ancestorWindowToFocus->SetFocusedElement(nullptr, 0, true); 2575 } 2576 2577 // When the focus of aBrowsingContextToClear is cleared, it should 2578 // also clear its ancestors's focus because ancestors should no longer 2579 // be considered aBrowsingContextToClear is focused. 2580 // 2581 // We don't need to do this when aBrowsingContextToClear and 2582 // aAncestorBrowsingContextToFocus is equal because ancestors don't 2583 // care about this. 2584 if (aBrowsingContextToClear && 2585 aBrowsingContextToClear != aAncestorBrowsingContextToFocus) { 2586 AdjustWindowFocus( 2587 aBrowsingContextToClear, false, 2588 IsWindowVisible(aBrowsingContextToClear->GetDOMWindow()), aActionId, 2589 true /* aShouldClearAncestorFocus */, 2590 aAncestorBrowsingContextToFocus); 2591 } 2592 } 2593 2594 SetFocusedWindowInternal(nullptr, aActionId); 2595 mFocusedElement = nullptr; 2596 2597 RefPtr<Document> doc = window->GetExtantDoc(); 2598 if (doc) { 2599 SendFocusOrBlurEvent(eBlur, presShell, doc, doc, false); 2600 } 2601 if (!GetFocusedBrowsingContext()) { 2602 nsCOMPtr<nsPIDOMWindowInner> innerWindow = 2603 window->GetCurrentInnerWindow(); 2604 // MOZ_KnownLive due to bug 1506441 2605 SendFocusOrBlurEvent( 2606 eBlur, presShell, doc, 2607 MOZ_KnownLive(nsGlobalWindowInner::Cast(innerWindow)), false); 2608 } 2609 2610 // check if a different window was focused 2611 result = (!GetFocusedBrowsingContext() && GetActiveBrowsingContext()); 2612 } else if (GetActiveBrowsingContext()) { 2613 // Otherwise, the blur of the element without blurring the document 2614 // occurred normally. Call UpdateCaret to redisplay the caret at the right 2615 // location within the document. This is needed to ensure that the caret 2616 // used for caret browsing is made visible again when an input field is 2617 // blurred. 2618 UpdateCaret(false, true, nullptr); 2619 } 2620 2621 return result; 2622 } 2623 2624 void nsFocusManager::ActivateRemoteFrameIfNeeded(Element& aElement, 2625 uint64_t aActionId) { 2626 if (BrowserParent* remote = BrowserParent::GetFrom(&aElement)) { 2627 remote->Activate(aActionId); 2628 LOGFOCUS( 2629 ("Remote browser activated %p, actionid: %" PRIu64, remote, aActionId)); 2630 } 2631 2632 // Same as above but for out-of-process iframes 2633 if (BrowserBridgeChild* bbc = BrowserBridgeChild::GetFrom(&aElement)) { 2634 bbc->Activate(aActionId); 2635 LOGFOCUS(("Out-of-process iframe activated %p, actionid: %" PRIu64, bbc, 2636 aActionId)); 2637 } 2638 } 2639 2640 void nsFocusManager::FixUpFocusBeforeFrameLoaderChange(Element& aElement, 2641 BrowsingContext* aBc) { 2642 // If focus is out of process we don't need to do anything. 2643 if (!mFocusedWindow || !aBc) { 2644 return; 2645 } 2646 auto* docShell = aBc->GetDocShell(); 2647 if (!docShell) { 2648 return; 2649 } 2650 if (!IsSameOrAncestor(docShell->GetWindow(), mFocusedWindow)) { 2651 // The window about to go away is not focused. 2652 return; 2653 } 2654 LOGFOCUS(("About to swap frame loaders on focused in-process window %p", 2655 mFocusedWindow.get())); 2656 mFocusedWindow = GetCurrentWindow(&aElement); 2657 mFocusedElement = &aElement; 2658 } 2659 2660 void nsFocusManager::FixUpFocusAfterFrameLoaderChange(Element& aElement) { 2661 MOZ_ASSERT(mFocusedElement == &aElement); 2662 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); 2663 if (GetContentWindow(&aElement)) { 2664 // This will focus the content window. 2665 SetFocusInner(&aElement, 0, false, false); 2666 } else { 2667 // If we're remote, activate the frame. 2668 ActivateRemoteFrameIfNeeded(aElement, GenerateFocusActionId()); 2669 } 2670 RefPtr<nsPresContext> presContext = aElement.OwnerDoc()->GetPresContext(); 2671 IMEStateManager::OnChangeFocus(presContext, &aElement, 2672 InputContextAction::CAUSE_UNKNOWN); 2673 } 2674 2675 void nsFocusManager::Focus( 2676 nsPIDOMWindowOuter* aWindow, Element* aElement, uint32_t aFlags, 2677 bool aIsNewDocument, bool aFocusChanged, bool aWindowRaised, 2678 bool aAdjustWidget, uint64_t aActionId, 2679 const Maybe<BlurredElementInfo>& aBlurredElementInfo) { 2680 LOGFOCUS(("<<Focus begin actionid: %" PRIu64 ">>", aActionId)); 2681 2682 if (!aWindow) { 2683 return; 2684 } 2685 2686 // Keep a reference to the presShell since dispatching the DOM event may 2687 // cause the document to be destroyed. 2688 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell(); 2689 if (!docShell) { 2690 return; 2691 } 2692 2693 const RefPtr<PresShell> presShell = docShell->GetPresShell(); 2694 if (!presShell) { 2695 return; 2696 } 2697 2698 bool focusInOtherContentProcess = false; 2699 // Keep mochitest-browser-chrome harness happy by ignoring 2700 // focusInOtherContentProcess in the chrome process, because the harness 2701 // expects that. 2702 if (!XRE_IsParentProcess()) { 2703 if (RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(aElement)) { 2704 // Only look at pre-existing browsing contexts. If this function is 2705 // called during reflow, calling GetBrowsingContext() could cause frame 2706 // loader initialization at a time when it isn't safe. 2707 if (BrowsingContext* bc = flo->GetExtantBrowsingContext()) { 2708 focusInOtherContentProcess = !bc->IsInProcess(); 2709 } 2710 } 2711 2712 if (ActionIdComparableAndLower( 2713 aActionId, mActionIdForFocusedBrowsingContextInContent)) { 2714 // Unclear if this ever happens. 2715 LOGFOCUS( 2716 ("Ignored an attempt to focus an element due to stale action id " 2717 "%" PRIu64 ".", 2718 aActionId)); 2719 return; 2720 } 2721 } 2722 2723 // If the focus actually changed, set the focus method (mouse, keyboard, etc). 2724 // Otherwise, just get the current focus method and use that. This ensures 2725 // that the method is set during the document and window focus events. 2726 uint32_t focusMethod = aFocusChanged 2727 ? aFlags & METHODANDRING_MASK 2728 : aWindow->GetFocusMethod() | 2729 (aFlags & (FLAG_SHOWRING | FLAG_NOSHOWRING)); 2730 2731 if (!IsWindowVisible(aWindow)) { 2732 // if the window isn't visible, for instance because it is a hidden tab, 2733 // update the current focus and scroll it into view but don't do anything 2734 // else 2735 if (aElement) { 2736 aWindow->SetFocusedElement(aElement, focusMethod); 2737 if (aFocusChanged) { 2738 ScrollIntoView(presShell, aElement, aFlags); 2739 } 2740 } 2741 return; 2742 } 2743 2744 LOGCONTENT("Element %s has been focused", aElement); 2745 2746 if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) { 2747 Document* docm = aWindow->GetExtantDoc(); 2748 if (docm) { 2749 LOGCONTENT(" from %s", docm->GetRootElement()); 2750 } 2751 LOGFOCUS( 2752 (" [Newdoc: %d FocusChanged: %d Raised: %d Flags: %x actionid: %" PRIu64 2753 "]", 2754 aIsNewDocument, aFocusChanged, aWindowRaised, aFlags, aActionId)); 2755 } 2756 2757 if (aIsNewDocument) { 2758 // if this is a new document, update the parent chain of frames so that 2759 // focus can be traversed from the top level down to the newly focused 2760 // window. 2761 RefPtr<BrowsingContext> bc = aWindow->GetBrowsingContext(); 2762 AdjustWindowFocus(bc, false, IsWindowVisible(aWindow), aActionId, 2763 false /* aShouldClearAncestorFocus */, 2764 nullptr /* aAncestorBrowsingContextToFocus */); 2765 } 2766 2767 // indicate that the window has taken focus. 2768 if (aWindow->TakeFocus(true, focusMethod)) { 2769 aIsNewDocument = true; 2770 } 2771 2772 SetFocusedWindowInternal(aWindow, aActionId); 2773 2774 if (aAdjustWidget && !sTestMode) { 2775 if (nsCOMPtr<nsIWidget> widget = presShell->GetRootWidget()) { 2776 widget->SetFocus(nsIWidget::Raise::No, aFlags & FLAG_NONSYSTEMCALLER 2777 ? CallerType::NonSystem 2778 : CallerType::System); 2779 } 2780 } 2781 2782 // if switching to a new document, first fire the focus event on the 2783 // document and then the window. 2784 if (aIsNewDocument) { 2785 RefPtr<Document> doc = aWindow->GetExtantDoc(); 2786 // The focus change should be notified to IMEStateManager from here if: 2787 // * the focused element is in design mode or 2788 // * nobody gets focus and the document is in design mode 2789 // since any element whose uncomposed document is in design mode won't 2790 // receive focus event. 2791 if (doc && ((aElement && aElement->IsInDesignMode()) || 2792 (!aElement && doc->IsInDesignMode()))) { 2793 RefPtr<nsPresContext> presContext = presShell->GetPresContext(); 2794 IMEStateManager::OnChangeFocus(presContext, nullptr, 2795 GetFocusMoveActionCause(aFlags)); 2796 } 2797 if (doc && !focusInOtherContentProcess) { 2798 SendFocusOrBlurEvent(eFocus, presShell, doc, doc, aWindowRaised); 2799 } 2800 if (GetFocusedBrowsingContext() == aWindow->GetBrowsingContext() && 2801 !mFocusedElement && !focusInOtherContentProcess) { 2802 nsCOMPtr<nsPIDOMWindowInner> innerWindow = 2803 aWindow->GetCurrentInnerWindow(); 2804 // MOZ_KnownLive due to bug 1506441 2805 SendFocusOrBlurEvent( 2806 eFocus, presShell, doc, 2807 MOZ_KnownLive(nsGlobalWindowInner::Cast(innerWindow)), aWindowRaised); 2808 } 2809 } 2810 2811 const RefPtr<Element> elementToFocus = [&]() -> Element* { 2812 if (!aElement || !aElement->IsInComposedDoc() || 2813 aElement->GetComposedDoc() != aWindow->GetExtantDoc()) { 2814 // Element moved documents, don't focus it to prevent redirecting focus to 2815 // the wrong window. 2816 return nullptr; 2817 } 2818 return aElement; 2819 }(); 2820 if (elementToFocus && !mFocusedElement && 2821 GetFocusedBrowsingContext() == aWindow->GetBrowsingContext()) { 2822 mFocusedElement = elementToFocus; 2823 2824 nsIContent* focusedNode = aWindow->GetFocusedElement(); 2825 const bool sendFocusEvent = elementToFocus->IsInComposedDoc() && 2826 !IsNonFocusableRoot(elementToFocus); 2827 const bool isRefocus = focusedNode && focusedNode == elementToFocus; 2828 const bool shouldShowFocusRing = 2829 sendFocusEvent && 2830 ShouldMatchFocusVisible(aWindow, *elementToFocus, aFlags); 2831 2832 aWindow->SetFocusedElement(elementToFocus, focusMethod, false); 2833 2834 const RefPtr<nsPresContext> presContext = presShell->GetPresContext(); 2835 if (sendFocusEvent) { 2836 NotifyFocusStateChange(elementToFocus, nullptr, aFlags, 2837 /* aGettingFocus = */ true, shouldShowFocusRing); 2838 2839 // If this is a remote browser, focus its widget and activate remote 2840 // content. Note that we might no longer be in the same document, 2841 // due to the events we fired above when aIsNewDocument. 2842 if (presShell->GetDocument() == elementToFocus->GetComposedDoc()) { 2843 ActivateRemoteFrameIfNeeded(*elementToFocus, aActionId); 2844 } 2845 2846 IMEStateManager::OnChangeFocus(presContext, elementToFocus, 2847 GetFocusMoveActionCause(aFlags)); 2848 2849 // as long as this focus wasn't because a window was raised, update the 2850 // commands 2851 // XXXndeakin P2 someone could adjust the focus during the update 2852 if (!aWindowRaised) { 2853 aWindow->UpdateCommands(u"focus"_ns); 2854 } 2855 2856 // If the focused element changed, scroll it into view 2857 if (aFocusChanged) { 2858 ScrollIntoView(presShell, elementToFocus, aFlags); 2859 } 2860 2861 if (!focusInOtherContentProcess) { 2862 RefPtr<Document> composedDocument = elementToFocus->GetComposedDoc(); 2863 RefPtr<Element> relatedTargetElement = 2864 aBlurredElementInfo ? aBlurredElementInfo->mElement.get() : nullptr; 2865 SendFocusOrBlurEvent(eFocus, presShell, composedDocument, 2866 elementToFocus, aWindowRaised, isRefocus, 2867 relatedTargetElement); 2868 } 2869 } else { 2870 // We should notify IMEStateManager of actual focused element even if it 2871 // won't get focus event because the other IMEStateManager users do not 2872 // want to depend on this check, but IMEStateManager wants to verify 2873 // passed focused element for avoidng to overrride nested calls. 2874 IMEStateManager::OnChangeFocus(presContext, elementToFocus, 2875 GetFocusMoveActionCause(aFlags)); 2876 if (!aWindowRaised) { 2877 aWindow->UpdateCommands(u"focus"_ns); 2878 } 2879 if (aFocusChanged) { 2880 // If the focused element changed, scroll it into view 2881 ScrollIntoView(presShell, elementToFocus, aFlags); 2882 } 2883 } 2884 } else { 2885 // We only need this on this branch, on the branch above 2886 // NotifyFocusStateChange takes care of it. 2887 MaybeFixUpFocusWithinState(elementToFocus, mFocusedElement); 2888 if (!mFocusedElement && mFocusedWindow == aWindow) { 2889 // When there is no focused element, IMEStateManager needs to adjust IME 2890 // enabled state with the document. 2891 RefPtr<nsPresContext> presContext = presShell->GetPresContext(); 2892 IMEStateManager::OnChangeFocus(presContext, nullptr, 2893 GetFocusMoveActionCause(aFlags)); 2894 } 2895 2896 if (!aWindowRaised) { 2897 aWindow->UpdateCommands(u"focus"_ns); 2898 } 2899 } 2900 2901 // update the caret visibility and position to match the newly focused 2902 // element. However, don't update the position if this was a focus due to a 2903 // mouse click as the selection code would already have moved the caret as 2904 // needed. If this is a different document than was focused before, also 2905 // update the caret's visibility. If this is the same document, the caret 2906 // visibility should be the same as before so there is no need to update it. 2907 if (mFocusedElement == elementToFocus) { 2908 RefPtr<Element> focusedElement = mFocusedElement; 2909 UpdateCaret(aFocusChanged && !(aFlags & FLAG_BYMOUSE), aIsNewDocument, 2910 focusedElement); 2911 } 2912 } 2913 2914 class FocusBlurEvent : public Runnable { 2915 public: 2916 FocusBlurEvent(EventTarget* aTarget, EventMessage aEventMessage, 2917 nsPresContext* aContext, bool aWindowRaised, bool aIsRefocus, 2918 EventTarget* aRelatedTarget) 2919 : mozilla::Runnable("FocusBlurEvent"), 2920 mTarget(aTarget), 2921 mContext(aContext), 2922 mEventMessage(aEventMessage), 2923 mWindowRaised(aWindowRaised), 2924 mIsRefocus(aIsRefocus), 2925 mRelatedTarget(aRelatedTarget) {} 2926 2927 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398) 2928 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override { 2929 InternalFocusEvent event(true, mEventMessage); 2930 event.mFlags.mBubbles = false; 2931 event.mFlags.mCancelable = false; 2932 event.mFromRaise = mWindowRaised; 2933 event.mIsRefocus = mIsRefocus; 2934 event.mRelatedTarget = mRelatedTarget; 2935 return EventDispatcher::Dispatch(mTarget, mContext, &event); 2936 } 2937 2938 const nsCOMPtr<EventTarget> mTarget; 2939 const RefPtr<nsPresContext> mContext; 2940 EventMessage mEventMessage; 2941 bool mWindowRaised; 2942 bool mIsRefocus; 2943 nsCOMPtr<EventTarget> mRelatedTarget; 2944 }; 2945 2946 class FocusInOutEvent : public Runnable { 2947 public: 2948 FocusInOutEvent(EventTarget* aTarget, EventMessage aEventMessage, 2949 nsPresContext* aContext, 2950 nsPIDOMWindowOuter* aOriginalFocusedWindow, 2951 nsIContent* aOriginalFocusedContent, 2952 EventTarget* aRelatedTarget) 2953 : mozilla::Runnable("FocusInOutEvent"), 2954 mTarget(aTarget), 2955 mContext(aContext), 2956 mEventMessage(aEventMessage), 2957 mOriginalFocusedWindow(aOriginalFocusedWindow), 2958 mOriginalFocusedContent(aOriginalFocusedContent), 2959 mRelatedTarget(aRelatedTarget) {} 2960 2961 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398) 2962 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override { 2963 nsCOMPtr<nsIContent> originalWindowFocus = 2964 mOriginalFocusedWindow ? mOriginalFocusedWindow->GetFocusedElement() 2965 : nullptr; 2966 // Blink does not check that focus is the same after blur, but WebKit does. 2967 // Opt to follow Blink's behavior (see bug 687787). 2968 if (mEventMessage == eFocusOut || 2969 originalWindowFocus == mOriginalFocusedContent) { 2970 InternalFocusEvent event(true, mEventMessage); 2971 event.mFlags.mBubbles = true; 2972 event.mFlags.mCancelable = false; 2973 event.mRelatedTarget = mRelatedTarget; 2974 return EventDispatcher::Dispatch(mTarget, mContext, &event); 2975 } 2976 return NS_OK; 2977 } 2978 2979 const nsCOMPtr<EventTarget> mTarget; 2980 const RefPtr<nsPresContext> mContext; 2981 EventMessage mEventMessage; 2982 nsCOMPtr<nsPIDOMWindowOuter> mOriginalFocusedWindow; 2983 nsCOMPtr<nsIContent> mOriginalFocusedContent; 2984 nsCOMPtr<EventTarget> mRelatedTarget; 2985 }; 2986 2987 static Document* GetDocumentHelper(EventTarget* aTarget) { 2988 if (!aTarget) { 2989 return nullptr; 2990 } 2991 if (const nsINode* node = nsINode::FromEventTarget(aTarget)) { 2992 return node->OwnerDoc(); 2993 } 2994 nsPIDOMWindowInner* win = nsPIDOMWindowInner::FromEventTarget(aTarget); 2995 return win ? win->GetExtantDoc() : nullptr; 2996 } 2997 2998 void nsFocusManager::FireFocusInOrOutEvent( 2999 EventMessage aEventMessage, PresShell* aPresShell, EventTarget* aTarget, 3000 nsPIDOMWindowOuter* aCurrentFocusedWindow, 3001 nsIContent* aCurrentFocusedContent, EventTarget* aRelatedTarget) { 3002 NS_ASSERTION(aEventMessage == eFocusIn || aEventMessage == eFocusOut, 3003 "Wrong event type for FireFocusInOrOutEvent"); 3004 3005 nsContentUtils::AddScriptRunner(new FocusInOutEvent( 3006 aTarget, aEventMessage, aPresShell->GetPresContext(), 3007 aCurrentFocusedWindow, aCurrentFocusedContent, aRelatedTarget)); 3008 } 3009 3010 void nsFocusManager::SendFocusOrBlurEvent(EventMessage aEventMessage, 3011 PresShell* aPresShell, 3012 Document* aDocument, 3013 EventTarget* aTarget, 3014 bool aWindowRaised, bool aIsRefocus, 3015 EventTarget* aRelatedTarget) { 3016 NS_ASSERTION(aEventMessage == eFocus || aEventMessage == eBlur, 3017 "Wrong event type for SendFocusOrBlurEvent"); 3018 3019 nsCOMPtr<Document> eventTargetDoc = GetDocumentHelper(aTarget); 3020 nsCOMPtr<Document> relatedTargetDoc = GetDocumentHelper(aRelatedTarget); 3021 3022 // set aRelatedTarget to null if it's not in the same document as aTarget 3023 if (eventTargetDoc != relatedTargetDoc) { 3024 aRelatedTarget = nullptr; 3025 } 3026 3027 if (aDocument && aDocument->EventHandlingSuppressed()) { 3028 // if this event was already queued, remove it and append it to the end 3029 mDelayedBlurFocusEvents.RemoveElementsBy([&](const auto& event) { 3030 return event.mEventMessage == aEventMessage && 3031 event.mPresShell == aPresShell && event.mDocument == aDocument && 3032 event.mTarget == aTarget && event.mRelatedTarget == aRelatedTarget; 3033 }); 3034 3035 mDelayedBlurFocusEvents.EmplaceBack(aEventMessage, aPresShell, aDocument, 3036 aTarget, aRelatedTarget); 3037 return; 3038 } 3039 3040 // If mDelayedBlurFocusEvents queue is not empty, check if there are events 3041 // that belongs to this doc, if yes, fire them first. 3042 if (aDocument && !aDocument->EventHandlingSuppressed() && 3043 mDelayedBlurFocusEvents.Length()) { 3044 FireDelayedEvents(aDocument); 3045 } 3046 3047 FireFocusOrBlurEvent(aEventMessage, aPresShell, aTarget, aWindowRaised, 3048 aIsRefocus, aRelatedTarget); 3049 } 3050 3051 void nsFocusManager::FireFocusOrBlurEvent(EventMessage aEventMessage, 3052 PresShell* aPresShell, 3053 EventTarget* aTarget, 3054 bool aWindowRaised, bool aIsRefocus, 3055 EventTarget* aRelatedTarget) { 3056 nsCOMPtr<nsPIDOMWindowOuter> currentWindow = mFocusedWindow; 3057 nsCOMPtr<nsPIDOMWindowInner> targetWindow = do_QueryInterface(aTarget); 3058 nsCOMPtr<Document> targetDocument = do_QueryInterface(aTarget); 3059 nsCOMPtr<nsIContent> currentFocusedContent = 3060 currentWindow ? currentWindow->GetFocusedElement() : nullptr; 3061 3062 #ifdef ACCESSIBILITY 3063 nsAccessibilityService* accService = GetAccService(); 3064 if (accService) { 3065 if (aEventMessage == eFocus) { 3066 accService->NotifyOfDOMFocus(aTarget); 3067 } else { 3068 accService->NotifyOfDOMBlur(aTarget); 3069 } 3070 } 3071 #endif 3072 3073 aPresShell->ScheduleContentRelevancyUpdate( 3074 ContentRelevancyReason::FocusInSubtree); 3075 3076 nsContentUtils::AddScriptRunner( 3077 new FocusBlurEvent(aTarget, aEventMessage, aPresShell->GetPresContext(), 3078 aWindowRaised, aIsRefocus, aRelatedTarget)); 3079 3080 // Check that the target is not a window or document before firing 3081 // focusin/focusout. Other browsers do not fire focusin/focusout on window, 3082 // despite being required in the spec, so follow their behavior. 3083 // 3084 // As for document, we should not even fire focus/blur, but until then, we 3085 // need this check. targetDocument should be removed once bug 1228802 is 3086 // resolved. 3087 if (!targetWindow && !targetDocument) { 3088 EventMessage focusInOrOutMessage = 3089 aEventMessage == eFocus ? eFocusIn : eFocusOut; 3090 FireFocusInOrOutEvent(focusInOrOutMessage, aPresShell, aTarget, 3091 currentWindow, currentFocusedContent, aRelatedTarget); 3092 } 3093 } 3094 3095 void nsFocusManager::ScrollIntoView(PresShell* aPresShell, nsIContent* aContent, 3096 uint32_t aFlags) { 3097 if (aFlags & FLAG_NOSCROLL) { 3098 return; 3099 } 3100 3101 // If the noscroll flag isn't set, scroll the newly focused element into view. 3102 const ScrollAxis axis(WhereToScroll::Center, WhenToScroll::IfNotVisible); 3103 aPresShell->ScrollContentIntoView(aContent, axis, axis, 3104 ScrollFlags::ScrollOverflowHidden); 3105 // Scroll the input / textarea selection into view, unless focused with the 3106 // mouse, see bug 572649. 3107 if (aFlags & FLAG_BYMOUSE) { 3108 return; 3109 } 3110 // ScrollContentIntoView flushes layout, so no need to flush again here. 3111 if (nsTextControlFrame* tf = do_QueryFrame(aContent->GetPrimaryFrame())) { 3112 tf->ScrollSelectionIntoViewAsync(nsTextControlFrame::ScrollAncestors::Yes); 3113 } 3114 } 3115 3116 void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow, 3117 CallerType aCallerType, uint64_t aActionId) { 3118 // don't raise windows that are already raised or are in the process of 3119 // being lowered 3120 3121 if (!aWindow || aWindow == mWindowBeingLowered) { 3122 return; 3123 } 3124 3125 if (XRE_IsParentProcess()) { 3126 if (aWindow == mActiveWindow) { 3127 if (!mFocusedWindow || 3128 !IsSameOrAncestor(aWindow->GetBrowsingContext(), 3129 mFocusedWindow->GetBrowsingContext())) { 3130 MoveFocusToWindowAfterRaise(aWindow, aActionId); 3131 } 3132 return; 3133 } 3134 } else { 3135 BrowsingContext* bc = aWindow->GetBrowsingContext(); 3136 // TODO: Deeper OOP frame hierarchies are 3137 // https://bugzilla.mozilla.org/show_bug.cgi?id=1661227 3138 if (bc == GetActiveBrowsingContext()) { 3139 return; 3140 } 3141 if (bc == GetFocusedBrowsingContext()) { 3142 return; 3143 } 3144 } 3145 3146 if (sTestMode) { 3147 // In test mode, emulate raising the window. WindowRaised takes 3148 // care of lowering the present active window. This happens in 3149 // a separate runnable to avoid touching multiple windows in 3150 // the current runnable. 3151 NS_DispatchToCurrentThread(NS_NewRunnableFunction( 3152 "nsFocusManager::RaiseWindow", 3153 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1770093) 3154 [self = RefPtr{this}, window = nsCOMPtr{aWindow}]() 3155 MOZ_CAN_RUN_SCRIPT_BOUNDARY -> void { 3156 self->WindowRaised(window, GenerateFocusActionId()); 3157 })); 3158 return; 3159 } 3160 3161 if (XRE_IsContentProcess()) { 3162 BrowsingContext* bc = aWindow->GetBrowsingContext(); 3163 if (!bc->IsTop()) { 3164 // Assume the raise below will succeed and run the raising synchronously 3165 // in this process to make the focus event that is observable in this 3166 // process fire in the right order relative to mouseup when we are here 3167 // thanks to a mousedown. 3168 WindowRaised(aWindow, aActionId); 3169 } 3170 } 3171 3172 nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = 3173 do_QueryInterface(aWindow->GetDocShell()); 3174 if (treeOwnerAsWin) { 3175 nsCOMPtr<nsIWidget> widget; 3176 treeOwnerAsWin->GetMainWidget(getter_AddRefs(widget)); 3177 if (widget) { 3178 widget->SetFocus(nsIWidget::Raise::Yes, aCallerType); 3179 } 3180 } 3181 } 3182 3183 void nsFocusManager::UpdateCaretForCaretBrowsingMode() { 3184 RefPtr<Element> focusedElement = mFocusedElement; 3185 UpdateCaret(false, true, focusedElement); 3186 } 3187 3188 void nsFocusManager::UpdateCaret(bool aMoveCaretToFocus, bool aUpdateVisibility, 3189 nsIContent* aContent) { 3190 LOGFOCUS(("Update Caret: %d %d", aMoveCaretToFocus, aUpdateVisibility)); 3191 3192 if (!mFocusedWindow) { 3193 return; 3194 } 3195 3196 // this is called when a document is focused or when the caretbrowsing 3197 // preference is changed 3198 nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell(); 3199 if (!focusedDocShell) { 3200 return; 3201 } 3202 3203 if (focusedDocShell->ItemType() == nsIDocShellTreeItem::typeChrome) { 3204 return; // Never browse with caret in chrome 3205 } 3206 3207 bool browseWithCaret = StaticPrefs::accessibility_browsewithcaret(); 3208 3209 const RefPtr<PresShell> presShell = focusedDocShell->GetPresShell(); 3210 if (!presShell) { 3211 return; 3212 } 3213 3214 // If this is an editable document which isn't contentEditable, or a 3215 // contentEditable document and the node to focus is contentEditable, 3216 // return, so that we don't mess with caret visibility. 3217 bool isEditable = false; 3218 focusedDocShell->GetEditable(&isEditable); 3219 3220 if (isEditable) { 3221 Document* doc = presShell->GetDocument(); 3222 3223 bool isContentEditableDoc = 3224 doc && 3225 doc->GetEditingState() == Document::EditingState::eContentEditable; 3226 3227 bool isFocusEditable = aContent && aContent->HasFlag(NODE_IS_EDITABLE); 3228 if (!isContentEditableDoc || isFocusEditable) { 3229 return; 3230 } 3231 } 3232 3233 if (!isEditable && aMoveCaretToFocus) { 3234 MoveCaretToFocus(presShell, aContent); 3235 } 3236 3237 // The above MoveCaretToFocus call may run scripts which 3238 // may clear mFocusWindow 3239 if (!mFocusedWindow) { 3240 return; 3241 } 3242 3243 if (!aUpdateVisibility) { 3244 return; 3245 } 3246 3247 // XXXndeakin this doesn't seem right. It should be checking for this only 3248 // on the nearest ancestor frame which is a chrome frame. But this is 3249 // what the existing code does, so just leave it for now. 3250 if (!browseWithCaret) { 3251 nsCOMPtr<Element> docElement = mFocusedWindow->GetFrameElementInternal(); 3252 if (docElement) 3253 browseWithCaret = docElement->AttrValueIs( 3254 kNameSpaceID_None, nsGkAtoms::showcaret, u"true"_ns, eCaseMatters); 3255 } 3256 3257 SetCaretVisible(presShell, browseWithCaret, aContent); 3258 } 3259 3260 void nsFocusManager::MoveCaretToFocus(PresShell* aPresShell, 3261 nsIContent* aContent) { 3262 nsCOMPtr<Document> doc = aPresShell->GetDocument(); 3263 if (doc) { 3264 RefPtr<nsFrameSelection> frameSelection = aPresShell->FrameSelection(); 3265 RefPtr<Selection> domSelection = &frameSelection->NormalSelection(); 3266 MOZ_ASSERT(domSelection); 3267 3268 // First clear the selection. This way, if there is no currently focused 3269 // content, the selection will just be cleared. 3270 domSelection->RemoveAllRanges(IgnoreErrors()); 3271 if (aContent) { 3272 ErrorResult rv; 3273 RefPtr<nsRange> newRange = doc->CreateRange(rv); 3274 if (NS_WARN_IF(rv.Failed())) { 3275 rv.SuppressException(); 3276 return; 3277 } 3278 3279 // Set the range to the start of the currently focused node 3280 // Make sure it's collapsed 3281 newRange->SelectNodeContents(*aContent, IgnoreErrors()); 3282 3283 if (!aContent->GetFirstChild() || aContent->IsHTMLFormControlElement()) { 3284 // If current focus node is a leaf, set range to before the 3285 // node by using the parent as a container. 3286 // This prevents it from appearing as selected. 3287 newRange->SetStartBefore(*aContent, IgnoreErrors()); 3288 newRange->SetEndBefore(*aContent, IgnoreErrors()); 3289 } 3290 domSelection->AddRangeAndSelectFramesAndNotifyListeners(*newRange, 3291 IgnoreErrors()); 3292 domSelection->CollapseToStart(IgnoreErrors()); 3293 } 3294 } 3295 } 3296 3297 nsresult nsFocusManager::SetCaretVisible(PresShell* aPresShell, bool aVisible, 3298 nsIContent* aContent) { 3299 // When browsing with caret, make sure caret is visible after new focus 3300 // Return early if there is no caret. This can happen for the testcase 3301 // for bug 308025 where a window is closed in a blur handler. 3302 RefPtr<nsCaret> caret = aPresShell->GetCaret(); 3303 if (!caret) { 3304 return NS_OK; 3305 } 3306 3307 bool caretVisible = caret->IsVisible(); 3308 if (!aVisible && !caretVisible) { 3309 return NS_OK; 3310 } 3311 3312 RefPtr<nsFrameSelection> frameSelection; 3313 if (aContent) { 3314 NS_ASSERTION(aContent->GetComposedDoc() == aPresShell->GetDocument(), 3315 "Wrong document?"); 3316 nsIFrame* focusFrame = aContent->GetPrimaryFrame(); 3317 if (focusFrame) { 3318 frameSelection = focusFrame->GetFrameSelection(); 3319 } 3320 } 3321 3322 RefPtr<nsFrameSelection> docFrameSelection = aPresShell->FrameSelection(); 3323 3324 if (docFrameSelection && caret && 3325 (frameSelection == docFrameSelection || !aContent)) { 3326 Selection& domSelection = docFrameSelection->NormalSelection(); 3327 3328 // First, hide the caret to prevent attempting to show it in 3329 // SetCaretDOMSelection 3330 aPresShell->SetCaretEnabled(false); 3331 3332 // Tell the caret which selection to use 3333 caret->SetSelection(&domSelection); 3334 3335 // In content, we need to set the caret. The only special case is edit 3336 // fields, which have a different frame selection from the document. 3337 // They will take care of making the caret visible themselves. 3338 3339 aPresShell->SetCaretReadOnly(false); 3340 aPresShell->SetCaretEnabled(aVisible); 3341 } 3342 3343 return NS_OK; 3344 } 3345 3346 void nsFocusManager::GetSelectionLocation(Document* aDocument, 3347 PresShell* aPresShell, 3348 nsIContent** aStartContent, 3349 nsIContent** aEndContent) { 3350 *aStartContent = *aEndContent = nullptr; 3351 3352 nsPresContext* presContext = aPresShell->GetPresContext(); 3353 NS_ASSERTION(presContext, "mPresContent is null!!"); 3354 3355 RefPtr<Selection> domSelection = 3356 &aPresShell->ConstFrameSelection()->NormalSelection(); 3357 MOZ_ASSERT(domSelection); 3358 3359 const nsRange* domRange = domSelection->GetRangeAt(0); 3360 if (!domRange || !domRange->IsPositioned()) { 3361 return; 3362 } 3363 nsIContent* start = nsIContent::FromNode(domRange->GetStartContainer()); 3364 nsIContent* end = nsIContent::FromNode(domRange->GetEndContainer()); 3365 if (nsIContent* child = domRange->StartRef().GetChildAtOffset()) { 3366 start = child; 3367 } 3368 if (nsIContent* child = domRange->EndRef().GetChildAtOffset()) { 3369 end = child; 3370 } 3371 3372 // Next check to see if our caret is at the very end of a text node. If so, 3373 // the caret is actually sitting in front of the next logical frame's primary 3374 // node - so for this case we need to change the content to that node. 3375 // Note that if the text does not have text frame, we do not need to retreive 3376 // caret frame. This could occur if text frame has only collapsisble white- 3377 // spaces and is around a block boundary or an ancestor of it is invisible. 3378 // XXX If there is a visible text sibling, should we return it in the former 3379 // case? 3380 if (auto* text = Text::FromNodeOrNull(start); 3381 text && text->GetPrimaryFrame() && 3382 text->TextDataLength() == domRange->StartOffset() && 3383 domSelection->IsCollapsed()) { 3384 nsIFrame* startFrame = start->GetPrimaryFrame(); 3385 // Yes, indeed we were at the end of the last node 3386 const Element* const limiter = 3387 domSelection && domSelection->GetAncestorLimiter() 3388 ? domSelection->GetAncestorLimiter() 3389 : nullptr; 3390 nsFrameIterator frameIterator(presContext, startFrame, 3391 nsFrameIterator::Type::Leaf, 3392 false, // aVisual 3393 false, // aLockInScrollView 3394 true, // aFollowOOFs 3395 false, // aSkipPopupChecks 3396 limiter); 3397 3398 nsIFrame* newCaretFrame = nullptr; 3399 nsIContent* newCaretContent = start; 3400 const bool endOfSelectionInStartNode = start == end; 3401 do { 3402 // Continue getting the next frame until the primary content for the 3403 // frame we are on changes - we don't want to be stuck in the same 3404 // place 3405 frameIterator.Next(); 3406 newCaretFrame = frameIterator.CurrentItem(); 3407 if (!newCaretFrame) { 3408 break; 3409 } 3410 newCaretContent = newCaretFrame->GetContent(); 3411 } while (!newCaretContent || newCaretContent == start); 3412 3413 if (newCaretFrame && newCaretContent) { 3414 // If the caret is exactly at the same position of the new frame, 3415 // then we can use the newCaretFrame and newCaretContent for our 3416 // position 3417 nsRect caretRect; 3418 if (nsIFrame* frame = nsCaret::GetGeometry(domSelection, &caretRect)) { 3419 nsPoint caretWidgetOffset; 3420 nsIWidget* widget = frame->GetNearestWidget(caretWidgetOffset); 3421 caretRect.MoveBy(caretWidgetOffset); 3422 nsPoint newCaretOffset; 3423 nsIWidget* newCaretWidget = 3424 newCaretFrame->GetNearestWidget(newCaretOffset); 3425 if (widget == newCaretWidget && caretRect.TopLeft() == newCaretOffset) { 3426 // The caret is at the start of the new element. 3427 startFrame = newCaretFrame; 3428 start = newCaretContent; 3429 if (endOfSelectionInStartNode) { 3430 end = newCaretContent; // Ensure end of selection is 3431 // not before start 3432 } 3433 } 3434 } 3435 } 3436 } 3437 3438 NS_IF_ADDREF(*aStartContent = start); 3439 NS_IF_ADDREF(*aEndContent = end); 3440 } 3441 3442 nsresult nsFocusManager::DetermineElementToMoveFocus( 3443 nsPIDOMWindowOuter* aWindow, nsIContent* aStartContent, int32_t aType, 3444 bool aNoParentTraversal, bool aNavigateByKey, nsIContent** aNextContent) { 3445 *aNextContent = nullptr; 3446 3447 // This is used for document navigation only. It will be set to true if we 3448 // start navigating from a starting point. If this starting point is near the 3449 // end of the document (for example, an element on a statusbar), and there 3450 // are no child documents or panels before the end of the document, then we 3451 // will need to ensure that we don't consider the root chrome window when we 3452 // loop around and instead find the next child document/panel, as focus is 3453 // already in that window. This flag will be cleared once we navigate into 3454 // another document. 3455 bool mayFocusRoot = (aStartContent != nullptr); 3456 3457 nsCOMPtr<nsIContent> startContent = aStartContent; 3458 if (!startContent && aType != MOVEFOCUS_CARET) { 3459 if (aType == MOVEFOCUS_FORWARDDOC || aType == MOVEFOCUS_BACKWARDDOC) { 3460 // When moving between documents, make sure to get the right 3461 // starting content in a descendant. 3462 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow; 3463 startContent = GetFocusedDescendant(aWindow, eIncludeAllDescendants, 3464 getter_AddRefs(focusedWindow)); 3465 } else if (aType != MOVEFOCUS_LASTDOC) { 3466 // Otherwise, start at the focused node. If MOVEFOCUS_LASTDOC is used, 3467 // then we are document-navigating backwards from chrome to the content 3468 // process, and we don't want to use this so that we start from the end 3469 // of the document. 3470 startContent = aWindow->GetFocusedElement(); 3471 } 3472 } 3473 3474 nsCOMPtr<Document> doc; 3475 if (startContent) 3476 doc = startContent->GetComposedDoc(); 3477 else 3478 doc = aWindow->GetExtantDoc(); 3479 if (!doc) return NS_OK; 3480 3481 // True if we are navigating by document (F6/Shift+F6) or false if we are 3482 // navigating by element (Tab/Shift+Tab). 3483 const bool forDocumentNavigation = 3484 aType == MOVEFOCUS_FORWARDDOC || aType == MOVEFOCUS_BACKWARDDOC || 3485 aType == MOVEFOCUS_FIRSTDOC || aType == MOVEFOCUS_LASTDOC; 3486 3487 // If moving to the root or first document, find the root element and return. 3488 if (aType == MOVEFOCUS_ROOT || aType == MOVEFOCUS_FIRSTDOC) { 3489 NS_IF_ADDREF(*aNextContent = GetRootForFocus(aWindow, doc, false, false)); 3490 if (!*aNextContent && aType == MOVEFOCUS_FIRSTDOC) { 3491 // When looking for the first document, if the root wasn't focusable, 3492 // find the next focusable document. 3493 aType = MOVEFOCUS_FORWARDDOC; 3494 } else { 3495 return NS_OK; 3496 } 3497 } 3498 3499 // rootElement and presShell may be set to sub-document's ones so that they 3500 // cannot be `const`. 3501 RefPtr<Element> rootElement = doc->GetRootElement(); 3502 NS_ENSURE_TRUE(rootElement, NS_OK); 3503 3504 RefPtr<PresShell> presShell = doc->GetPresShell(); 3505 NS_ENSURE_TRUE(presShell, NS_OK); 3506 3507 if (aType == MOVEFOCUS_FIRST) { 3508 if (!aStartContent) { 3509 startContent = rootElement; 3510 } 3511 return GetNextTabbableContent(presShell, startContent, nullptr, 3512 startContent, true, 1, false, false, 3513 aNavigateByKey, false, false, aNextContent); 3514 } 3515 if (aType == MOVEFOCUS_LAST) { 3516 if (!aStartContent) { 3517 startContent = rootElement; 3518 } 3519 return GetNextTabbableContent(presShell, startContent, nullptr, 3520 startContent, false, 0, false, false, 3521 aNavigateByKey, false, false, aNextContent); 3522 } 3523 3524 bool forward = (aType == MOVEFOCUS_FORWARD || aType == MOVEFOCUS_FORWARDDOC || 3525 aType == MOVEFOCUS_CARET); 3526 bool doNavigation = true; 3527 bool ignoreTabIndex = false; 3528 // when a popup is open, we want to ensure that tab navigation occurs only 3529 // within the most recently opened panel. If a popup is open, its frame will 3530 // be stored in popupFrame. 3531 nsIFrame* popupFrame = nullptr; 3532 3533 int32_t tabIndex = forward ? 1 : 0; 3534 if (startContent) { 3535 nsIFrame* frame = startContent->GetPrimaryFrame(); 3536 tabIndex = (frame && !startContent->IsHTMLElement(nsGkAtoms::area)) 3537 ? frame->IsFocusable().mTabIndex 3538 : startContent->IsFocusableWithoutStyle().mTabIndex; 3539 3540 // if the current element isn't tabbable, ignore the tabindex and just 3541 // look for the next element. The root content won't have a tabindex 3542 // so just treat this as the beginning of the tab order. 3543 if (tabIndex < 0) { 3544 tabIndex = 1; 3545 if (startContent != rootElement) { 3546 ignoreTabIndex = true; 3547 } 3548 } 3549 3550 // check if the focus is currently inside a popup. Elements such as the 3551 // autocomplete widget use the noautofocus attribute to allow the focus to 3552 // remain outside the popup when it is opened. 3553 if (frame) { 3554 popupFrame = nsLayoutUtils::GetClosestFrameOfType( 3555 frame, LayoutFrameType::MenuPopup); 3556 } 3557 3558 if (popupFrame && !forDocumentNavigation) { 3559 // Don't navigate outside of a popup, so pretend that the 3560 // root content is the popup itself 3561 rootElement = popupFrame->GetContent()->AsElement(); 3562 NS_ASSERTION(rootElement, "Popup frame doesn't have a content node"); 3563 } else if (!forward) { 3564 // If focus moves backward and when current focused node is root 3565 // content or <body> element which is editable by contenteditable 3566 // attribute, focus should move to its parent document. 3567 if (startContent == rootElement) { 3568 doNavigation = false; 3569 } else { 3570 Document* doc = startContent->GetComposedDoc(); 3571 if (startContent == 3572 nsLayoutUtils::GetEditableRootContentByContentEditable(doc)) { 3573 doNavigation = false; 3574 } 3575 } 3576 } 3577 } else { 3578 if (aType != MOVEFOCUS_CARET) { 3579 // if there is no focus, yet a panel is open, focus the first item in 3580 // the panel 3581 nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 3582 if (pm) { 3583 popupFrame = pm->GetTopPopup(PopupType::Panel); 3584 } 3585 } 3586 if (popupFrame) { 3587 // When there is a popup open, and no starting content, start the search 3588 // at the topmost popup. 3589 startContent = popupFrame->GetContent(); 3590 NS_ASSERTION(startContent, "Popup frame doesn't have a content node"); 3591 // Unless we are searching for documents, set the root content to the 3592 // popup as well, so that we don't tab-navigate outside the popup. 3593 // When navigating by documents, we start at the popup but can navigate 3594 // outside of it to look for other panels and documents. 3595 if (!forDocumentNavigation) { 3596 rootElement = startContent->AsElement(); 3597 } 3598 3599 doc = startContent ? startContent->GetComposedDoc() : nullptr; 3600 } else { 3601 // Otherwise, for content shells, start from the location of the caret. 3602 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell(); 3603 if (docShell && docShell->ItemType() != nsIDocShellTreeItem::typeChrome) { 3604 nsCOMPtr<nsIContent> endSelectionContent; 3605 GetSelectionLocation(doc, presShell, getter_AddRefs(startContent), 3606 getter_AddRefs(endSelectionContent)); 3607 // If the selection is on the rootElement, then there is no selection 3608 if (startContent == rootElement) { 3609 startContent = nullptr; 3610 } 3611 3612 if (aType == MOVEFOCUS_CARET) { 3613 // GetFocusInSelection finds a focusable link near the caret. 3614 // If there is no start content though, don't do this to avoid 3615 // focusing something unexpected. 3616 if (startContent) { 3617 GetFocusInSelection(aWindow, startContent, endSelectionContent, 3618 aNextContent); 3619 } 3620 return NS_OK; 3621 } 3622 3623 if (startContent) { 3624 // when starting from a selection, we always want to find the next or 3625 // previous element in the document. So the tabindex on elements 3626 // should be ignored. 3627 ignoreTabIndex = true; 3628 // If selection starts from a focusable and tabbable element, we want 3629 // to make it focused rather than next/previous one. 3630 if (startContent->IsElement() && startContent->GetPrimaryFrame() && 3631 startContent->GetPrimaryFrame()->IsFocusable().IsTabbable()) { 3632 startContent = 3633 forward ? (startContent->GetPreviousSibling() 3634 ? startContent->GetPreviousSibling() 3635 // We don't need to get previous leaf node 3636 // because it may be too far from 3637 // startContent. We just want the previous 3638 // node immediately before startContent. 3639 : startContent->GetParent()) 3640 // We want the next node immdiately after startContent. 3641 // Therefore, we don't want its first child. 3642 : startContent->GetNextNonChildNode(); 3643 // If we reached the root element, we should treat it as there is no 3644 // selection as same as above. 3645 if (startContent == rootElement) { 3646 startContent = nullptr; 3647 } 3648 } 3649 } 3650 } 3651 3652 if (!startContent) { 3653 // otherwise, just use the root content as the starting point 3654 startContent = rootElement; 3655 NS_ENSURE_TRUE(startContent, NS_OK); 3656 } 3657 } 3658 } 3659 3660 // Check if the starting content is the same as the content assigned to the 3661 // retargetdocumentfocus attribute. Is so, we don't want to start searching 3662 // from there but instead from the beginning of the document. Otherwise, the 3663 // content that appears before the retargetdocumentfocus element will never 3664 // get checked as it will be skipped when the focus is retargetted to it. 3665 if (forDocumentNavigation && nsContentUtils::IsChromeDoc(doc)) { 3666 nsAutoString retarget; 3667 3668 if (rootElement->GetAttr(nsGkAtoms::retargetdocumentfocus, retarget)) { 3669 nsIContent* retargetElement = doc->GetElementById(retarget); 3670 // The common case here is the urlbar where focus is on the anonymous 3671 // input inside the textbox, but the retargetdocumentfocus attribute 3672 // refers to the textbox. The Contains check will return false and the 3673 // IsInclusiveDescendantOf check will return true in this case. 3674 if (retargetElement && 3675 (retargetElement == startContent || 3676 (!retargetElement->Contains(startContent) && 3677 startContent->IsInclusiveDescendantOf(retargetElement)))) { 3678 startContent = rootElement; 3679 } 3680 } 3681 } 3682 3683 NS_ASSERTION(startContent, "starting content not set"); 3684 3685 // keep a reference to the starting content. If we find that again, it means 3686 // we've iterated around completely and we don't want to adjust the focus. 3687 // The skipOriginalContentCheck will be set to true only for the first time 3688 // GetNextTabbableContent is called. This ensures that we don't break out 3689 // when nothing is focused to start with. Specifically, 3690 // GetNextTabbableContent first checks the root content -- which happens to 3691 // be the same as the start content -- when nothing is focused and tabbing 3692 // forward. Without skipOriginalContentCheck set to true, we'd end up 3693 // returning right away and focusing nothing. Luckily, GetNextTabbableContent 3694 // will never wrap around on its own, and can only return the original 3695 // content when it is called a second time or later. 3696 bool skipOriginalContentCheck = true; 3697 const nsCOMPtr<nsIContent> originalStartContent = startContent; 3698 3699 LOGCONTENTNAVIGATION("Focus Navigation Start Content %s", startContent.get()); 3700 LOGFOCUSNAVIGATION((" Forward: %d Tabindex: %d Ignore: %d DocNav: %d", 3701 forward, tabIndex, ignoreTabIndex, 3702 forDocumentNavigation)); 3703 3704 while (doc) { 3705 if (doNavigation) { 3706 nsCOMPtr<nsIContent> nextFocus; 3707 // TODO: MOZ_KnownLive is reruired due to bug 1770680 3708 nsresult rv = GetNextTabbableContent( 3709 presShell, rootElement, 3710 MOZ_KnownLive(skipOriginalContentCheck ? nullptr 3711 : originalStartContent.get()), 3712 startContent, forward, tabIndex, ignoreTabIndex, 3713 forDocumentNavigation, aNavigateByKey, false, false, 3714 getter_AddRefs(nextFocus)); 3715 NS_ENSURE_SUCCESS(rv, rv); 3716 if (rv == NS_SUCCESS_DOM_NO_OPERATION) { 3717 // Navigation was redirected to a child process, so just return. 3718 return NS_OK; 3719 } 3720 3721 // found a content node to focus. 3722 if (nextFocus) { 3723 LOGCONTENTNAVIGATION("Next Content: %s", nextFocus.get()); 3724 3725 // as long as the found node was not the same as the starting node, 3726 // set it as the return value. For document navigation, we can return 3727 // the same element in case there is only one content node that could 3728 // be returned, for example, in a child process document. 3729 if (nextFocus != originalStartContent || forDocumentNavigation) { 3730 nextFocus.forget(aNextContent); 3731 } 3732 return NS_OK; 3733 } 3734 3735 if (popupFrame && !forDocumentNavigation) { 3736 // in a popup, so start again from the beginning of the popup. However, 3737 // if we already started at the beginning, then there isn't anything to 3738 // focus, so just return 3739 if (startContent != rootElement) { 3740 startContent = rootElement; 3741 tabIndex = forward ? 1 : 0; 3742 continue; 3743 } 3744 return NS_OK; 3745 } 3746 } 3747 3748 doNavigation = true; 3749 skipOriginalContentCheck = forDocumentNavigation; 3750 ignoreTabIndex = false; 3751 3752 if (aNoParentTraversal) { 3753 if (startContent == rootElement) { 3754 return NS_OK; 3755 } 3756 3757 startContent = rootElement; 3758 tabIndex = forward ? 1 : 0; 3759 continue; 3760 } 3761 3762 // Reached the beginning or end of the document. Next, navigate up to the 3763 // parent document and try again. 3764 nsCOMPtr<nsPIDOMWindowOuter> piWindow = doc->GetWindow(); 3765 NS_ENSURE_TRUE(piWindow, NS_ERROR_FAILURE); 3766 3767 nsCOMPtr<nsIDocShell> docShell = piWindow->GetDocShell(); 3768 NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); 3769 3770 // Get the frame element this window is inside and, from that, get the 3771 // parent document and presshell. If there is no enclosing frame element, 3772 // then this is a top-level, embedded or remote window. 3773 startContent = piWindow->GetFrameElementInternal(); 3774 if (startContent) { 3775 doc = startContent->GetComposedDoc(); 3776 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); 3777 3778 rootElement = doc->GetRootElement(); 3779 presShell = doc->GetPresShell(); 3780 3781 // We can focus the root element now that we have moved to another 3782 // document. 3783 mayFocusRoot = true; 3784 3785 nsIFrame* frame = startContent->GetPrimaryFrame(); 3786 if (!frame) { 3787 return NS_OK; 3788 } 3789 3790 tabIndex = frame->IsFocusable().mTabIndex; 3791 if (tabIndex < 0) { 3792 tabIndex = 1; 3793 ignoreTabIndex = true; 3794 } 3795 3796 // if the frame is inside a popup, make sure to scan only within the 3797 // popup. This handles the situation of tabbing amongst elements 3798 // inside an iframe which is itself inside a popup. Otherwise, 3799 // navigation would move outside the popup when tabbing outside the 3800 // iframe. 3801 if (!forDocumentNavigation) { 3802 popupFrame = nsLayoutUtils::GetClosestFrameOfType( 3803 frame, LayoutFrameType::MenuPopup); 3804 if (popupFrame) { 3805 rootElement = popupFrame->GetContent()->AsElement(); 3806 NS_ASSERTION(rootElement, "Popup frame doesn't have a content node"); 3807 } 3808 } 3809 } else { 3810 if (aNavigateByKey) { 3811 // There is no parent, so move the focus to the parent process. 3812 if (auto* child = BrowserChild::GetFrom(docShell)) { 3813 child->SendMoveFocus(forward, forDocumentNavigation); 3814 // Blur the current element. 3815 RefPtr<BrowsingContext> focusedBC = GetFocusedBrowsingContext(); 3816 if (focusedBC && focusedBC->IsInProcess()) { 3817 Blur(focusedBC, nullptr, true, true, false, 3818 GenerateFocusActionId()); 3819 } else { 3820 nsCOMPtr<nsPIDOMWindowOuter> window = docShell->GetWindow(); 3821 window->SetFocusedElement(nullptr); 3822 } 3823 return NS_OK; 3824 } 3825 } 3826 3827 // If we have reached the end of the top-level document, focus the 3828 // first element in the top-level document. This should always happen 3829 // when navigating by document forwards but when navigating backwards, 3830 // only do this if we started in another document or within a popup frame. 3831 // If the focus started in this window outside a popup however, we should 3832 // continue by looping around to the end again. 3833 if (forDocumentNavigation && (forward || mayFocusRoot || popupFrame)) { 3834 // HTML content documents can have their root element focused by 3835 // pressing F6(a focus ring appears around the entire content area 3836 // frame). This root appears in the tab order before all of the elements 3837 // in the document. Chrome documents however cannot be focused directly, 3838 // so instead we focus the first focusable element within the window. 3839 // For example, the urlbar. 3840 RefPtr<Element> rootElementForFocus = 3841 GetRootForFocus(piWindow, doc, true, true); 3842 return FocusFirst(rootElementForFocus, aNextContent, 3843 true /* aReachedToEndForDocumentNavigation */); 3844 } 3845 3846 // Once we have hit the top-level and have iterated to the end again, we 3847 // just want to break out next time we hit this spot to prevent infinite 3848 // iteration. 3849 mayFocusRoot = true; 3850 3851 // reset the tab index and start again from the beginning or end 3852 startContent = rootElement; 3853 tabIndex = forward ? 1 : 0; 3854 } 3855 3856 // wrapped all the way around and didn't find anything to move the focus 3857 // to, so just break out 3858 if (startContent == originalStartContent) { 3859 break; 3860 } 3861 } 3862 3863 return NS_OK; 3864 } 3865 3866 uint32_t nsFocusManager::ProgrammaticFocusFlags(const FocusOptions& aOptions) { 3867 uint32_t flags = FLAG_BYJS; 3868 if (aOptions.mPreventScroll) { 3869 flags |= FLAG_NOSCROLL; 3870 } 3871 if (aOptions.mFocusVisible.WasPassed()) { 3872 flags |= aOptions.mFocusVisible.Value() ? FLAG_SHOWRING : FLAG_NOSHOWRING; 3873 } 3874 if (UserActivation::IsHandlingKeyboardInput()) { 3875 flags |= FLAG_BYKEY; 3876 } 3877 // TODO: We could do a similar thing if we're handling mouse input, but that 3878 // changes focusability of some elements so may be more risky. 3879 return flags; 3880 } 3881 3882 static bool IsHostOrSlot(const nsIContent* aContent) { 3883 return aContent && (aContent->GetShadowRoot() || 3884 aContent->IsHTMLElement(nsGkAtoms::slot)); 3885 } 3886 3887 // Helper class to iterate contents in scope by traversing flattened tree 3888 // in tree order 3889 class MOZ_STACK_CLASS ScopedContentTraversal { 3890 public: 3891 ScopedContentTraversal(nsIContent* aStartContent, nsIContent* aOwner) 3892 : mCurrent(aStartContent), mOwner(aOwner) { 3893 MOZ_ASSERT(aStartContent); 3894 } 3895 3896 void Next(); 3897 void Prev(); 3898 3899 void Reset() { SetCurrent(mOwner); } 3900 3901 nsIContent* GetCurrent() const { return mCurrent; } 3902 3903 private: 3904 void SetCurrent(nsIContent* aContent) { mCurrent = aContent; } 3905 3906 nsIContent* mCurrent; 3907 nsIContent* mOwner; 3908 }; 3909 3910 void ScopedContentTraversal::Next() { 3911 MOZ_ASSERT(mCurrent); 3912 3913 // Get mCurrent's first child if it's in the same scope. 3914 if (!IsHostOrSlot(mCurrent) || mCurrent == mOwner) { 3915 StyleChildrenIterator iter(mCurrent); 3916 nsIContent* child = iter.GetNextChild(); 3917 if (child) { 3918 SetCurrent(child); 3919 return; 3920 } 3921 } 3922 3923 // If mOwner has no children, END traversal 3924 if (mCurrent == mOwner) { 3925 SetCurrent(nullptr); 3926 return; 3927 } 3928 3929 nsIContent* current = mCurrent; 3930 while (1) { 3931 // Create parent's iterator and move to current 3932 nsIContent* parent = current->GetFlattenedTreeParent(); 3933 StyleChildrenIterator parentIter(parent); 3934 parentIter.Seek(current); 3935 3936 // Get next sibling of current 3937 if (nsIContent* next = parentIter.GetNextChild()) { 3938 SetCurrent(next); 3939 return; 3940 } 3941 3942 // If no next sibling and parent is mOwner, END traversal 3943 if (parent == mOwner) { 3944 SetCurrent(nullptr); 3945 return; 3946 } 3947 3948 current = parent; 3949 } 3950 } 3951 3952 void ScopedContentTraversal::Prev() { 3953 MOZ_ASSERT(mCurrent); 3954 3955 nsIContent* parent; 3956 nsIContent* last; 3957 if (mCurrent == mOwner) { 3958 // Get last child of mOwner 3959 StyleChildrenIterator ownerIter(mOwner, false /* aStartAtBeginning */); 3960 last = ownerIter.GetPreviousChild(); 3961 3962 parent = last; 3963 } else { 3964 // Create parent's iterator and move to mCurrent 3965 parent = mCurrent->GetFlattenedTreeParent(); 3966 StyleChildrenIterator parentIter(parent); 3967 parentIter.Seek(mCurrent); 3968 3969 // Get previous sibling 3970 last = parentIter.GetPreviousChild(); 3971 } 3972 3973 while (last) { 3974 parent = last; 3975 if (IsHostOrSlot(parent)) { 3976 // Skip contents in other scopes 3977 break; 3978 } 3979 3980 // Find last child 3981 StyleChildrenIterator iter(parent, false /* aStartAtBeginning */); 3982 last = iter.GetPreviousChild(); 3983 } 3984 3985 // If parent is mOwner and no previous sibling remains, END traversal 3986 SetCurrent(parent == mOwner ? nullptr : parent); 3987 } 3988 3989 static bool IsOpenPopoverWithInvoker(const nsIContent* aContent) { 3990 if (auto* popover = Element::FromNode(aContent)) { 3991 return popover && popover->IsPopoverOpen() && 3992 popover->GetPopoverData()->GetInvoker(); 3993 } 3994 return false; 3995 } 3996 3997 static nsGenericHTMLElement* GetAssociatedPopoverFromInvoker( 3998 nsIContent* aContent) { 3999 Element* invoker = Element::FromNode(aContent); 4000 if (!invoker) { 4001 return nullptr; 4002 } 4003 nsGenericHTMLElement* popover = invoker->GetAssociatedPopover(); 4004 if (popover && popover->IsPopoverOpen()) { 4005 MOZ_ASSERT(popover->GetPopoverData()->GetInvoker() == invoker); 4006 return popover; 4007 } 4008 return nullptr; 4009 } 4010 4011 static nsIContent* InvokerForPopoverShowingState(nsIContent* aContent) { 4012 if (aContent && GetAssociatedPopoverFromInvoker(aContent)) { 4013 return aContent; 4014 } 4015 return nullptr; 4016 } 4017 4018 /** 4019 * Returns true if the content is a Document, Host, Slot or Open popover with an 4020 * invoker */ 4021 static bool IsScopeOwner(const nsIContent* aContent) { 4022 return aContent && (IsHostOrSlot(aContent) || aContent->IsDocument() || 4023 IsOpenPopoverWithInvoker(aContent)); 4024 } 4025 4026 /** 4027 * Returns scope owner of aContent. 4028 * A scope owner is either a shadow host, or slot, or an open popover with a 4029 * trigger. See https://html.spec.whatwg.org/#focus-navigation-scope-owner. 4030 * While FindScopeOwner adheres to this part of the spec, some issues remain 4031 * especially around tabindex; see 4032 * https://bugzilla.mozilla.org/show_bug.cgi?id=1955857. 4033 */ 4034 static nsIContent* FindScopeOwner(nsIContent* aContent) { 4035 nsIContent* currentContent = aContent; 4036 while (currentContent) { 4037 nsIContent* parent = currentContent->GetFlattenedTreeParent(); 4038 4039 // 2. If element's parent is a shadow host, then return element's assigned 4040 // slot. 4041 // 3. If element's parent is a shadow root, then return the parent's host. 4042 // 4. If element's parent is the document element, then return the parent's 4043 // node document. 4044 // 5. If element is in the popover showing state and has a popover trigger 4045 // set, then return element's popover trigger. 4046 if (IsScopeOwner(parent)) { 4047 return parent; 4048 } 4049 4050 currentContent = parent; 4051 } 4052 4053 // 1. If element's parent is null, then return null. 4054 return nullptr; 4055 } 4056 4057 /** 4058 * Host and Slot elements need to be handled as if they had tabindex 0 even 4059 * when they don't have the attribute. This is a helper method to get the 4060 * right value for focus navigation. If aIsFocusable is passed, it is set to 4061 * true if the element itself is focusable. 4062 */ 4063 static int32_t HostOrSlotTabIndexValue(const nsIContent* aContent, 4064 bool* aIsFocusable = nullptr) { 4065 MOZ_ASSERT(IsHostOrSlot(aContent)); 4066 4067 if (aIsFocusable) { 4068 nsIFrame* frame = aContent->GetPrimaryFrame(); 4069 *aIsFocusable = frame && frame->IsFocusable().mTabIndex >= 0; 4070 } 4071 4072 const nsAttrValue* attrVal = 4073 aContent->AsElement()->GetParsedAttr(nsGkAtoms::tabindex); 4074 if (!attrVal) { 4075 return 0; 4076 } 4077 4078 if (attrVal->Type() == nsAttrValue::eInteger) { 4079 return attrVal->GetIntegerValue(); 4080 } 4081 4082 return -1; 4083 } 4084 4085 nsIContent* nsFocusManager::GetNextTabbableContentInScope( 4086 nsIContent* aOwner, nsIContent* aStartContent, 4087 nsIContent* aOriginalStartContent, bool aForward, int32_t aCurrentTabIndex, 4088 bool aIgnoreTabIndex, bool aForDocumentNavigation, bool aNavigateByKey, 4089 bool aSkipOwner, bool aReachedToEndForDocumentNavigation) { 4090 MOZ_ASSERT(aOwner, "aOwner must not be null"); 4091 MOZ_ASSERT( 4092 IsHostOrSlot(aOwner) || IsOpenPopoverWithInvoker(aOwner), 4093 "Scope owner should be host, slot or an open popover with invoker set."); 4094 4095 // XXX: Why don't we ignore tabindex when the current tabindex < 0? 4096 MOZ_ASSERT_IF(aCurrentTabIndex < 0, aIgnoreTabIndex); 4097 4098 if (!aSkipOwner && (aForward && aOwner == aStartContent)) { 4099 if (nsIFrame* frame = aOwner->GetPrimaryFrame()) { 4100 auto focusable = frame->IsFocusable(); 4101 if (focusable && focusable.mTabIndex >= 0) { 4102 return aOwner; 4103 } 4104 } 4105 } 4106 4107 // 4108 // Iterate contents in scope 4109 // 4110 ScopedContentTraversal contentTraversal(aStartContent, aOwner); 4111 nsCOMPtr<nsIContent> iterContent; 4112 nsIContent* firstNonChromeOnly = 4113 aStartContent->IsInNativeAnonymousSubtree() 4114 ? aStartContent->FindFirstNonChromeOnlyAccessContent() 4115 : nullptr; 4116 while (1) { 4117 // Iterate tab index to find corresponding contents in scope 4118 4119 while (1) { 4120 // Iterate remaining contents in scope to find next content to focus 4121 4122 // Get next content 4123 aForward ? contentTraversal.Next() : contentTraversal.Prev(); 4124 iterContent = contentTraversal.GetCurrent(); 4125 4126 if (firstNonChromeOnly && firstNonChromeOnly == iterContent) { 4127 // We just broke out from the native anonymous content, so move 4128 // to the previous/next node of the native anonymous owner. 4129 if (aForward) { 4130 contentTraversal.Next(); 4131 } else { 4132 contentTraversal.Prev(); 4133 } 4134 iterContent = contentTraversal.GetCurrent(); 4135 } 4136 if (!iterContent) { 4137 // Reach the end 4138 break; 4139 } 4140 4141 int32_t tabIndex = 0; 4142 if (IsHostOrSlot(iterContent)) { 4143 tabIndex = HostOrSlotTabIndexValue(iterContent); 4144 } else { 4145 nsIFrame* frame = iterContent->GetPrimaryFrame(); 4146 if (!frame) { 4147 continue; 4148 } 4149 tabIndex = frame->IsFocusable().mTabIndex; 4150 } 4151 if (tabIndex < 0 || !(aIgnoreTabIndex || tabIndex == aCurrentTabIndex)) { 4152 continue; 4153 } 4154 4155 if (!IsHostOrSlot(iterContent)) { 4156 nsCOMPtr<nsIContent> elementInFrame; 4157 bool checkSubDocument = true; 4158 if (aForDocumentNavigation && 4159 TryDocumentNavigation(iterContent, &checkSubDocument, 4160 getter_AddRefs(elementInFrame))) { 4161 return elementInFrame; 4162 } 4163 if (!checkSubDocument) { 4164 if (aReachedToEndForDocumentNavigation && 4165 nsContentUtils::IsChromeDoc(iterContent->GetComposedDoc())) { 4166 // aReachedToEndForDocumentNavigation is true means 4167 // 1. This is a document navigation (i.e, VK_F6, Control + Tab) 4168 // 2. This is the top-level document (Note that we may start from 4169 // a subdocument) 4170 // 3. We've searched through the this top-level document already 4171 if (!GetRootForChildDocument(iterContent)) { 4172 // We'd like to focus the first focusable element of this 4173 // top-level chrome document. 4174 return iterContent; 4175 } 4176 } 4177 continue; 4178 } 4179 4180 if (TryToMoveFocusToSubDocument(iterContent, aOriginalStartContent, 4181 aForward, aForDocumentNavigation, 4182 aNavigateByKey, 4183 aReachedToEndForDocumentNavigation, 4184 getter_AddRefs(elementInFrame))) { 4185 return elementInFrame; 4186 } 4187 4188 // Found content to focus 4189 return iterContent; 4190 } 4191 4192 // Search in scope owned by iterContent 4193 nsIContent* contentToFocus = GetNextTabbableContentInScope( 4194 iterContent, iterContent, aOriginalStartContent, aForward, 4195 aForward ? 1 : 0, aIgnoreTabIndex, aForDocumentNavigation, 4196 aNavigateByKey, false /* aSkipOwner */, 4197 aReachedToEndForDocumentNavigation); 4198 if (contentToFocus) { 4199 return contentToFocus; 4200 } 4201 }; 4202 4203 // If already at lowest priority tab (0), end search completely. 4204 // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0 4205 if (aCurrentTabIndex == (aForward ? 0 : 1)) { 4206 break; 4207 } 4208 4209 // We've been just trying to find some focusable element, and haven't, so 4210 // bail out. 4211 if (aIgnoreTabIndex) { 4212 break; 4213 } 4214 4215 // Continue looking for next highest priority tabindex 4216 aCurrentTabIndex = GetNextTabIndex(aOwner, aCurrentTabIndex, aForward); 4217 contentTraversal.Reset(); 4218 } 4219 4220 // Return scope owner at last for backward navigation if its tabindex 4221 // is non-negative 4222 if (!aSkipOwner && !aForward) { 4223 if (nsIFrame* frame = aOwner->GetPrimaryFrame()) { 4224 auto focusable = frame->IsFocusable(); 4225 if (focusable && focusable.mTabIndex >= 0) { 4226 return aOwner; 4227 } 4228 } 4229 } 4230 4231 return nullptr; 4232 } 4233 4234 nsIContent* nsFocusManager::GetNextTabbableContentInAncestorScopes( 4235 nsIContent* aStartOwner, nsCOMPtr<nsIContent>& aStartContent /* inout */, 4236 nsIContent* aOriginalStartContent, bool aForward, int32_t* aCurrentTabIndex, 4237 bool* aIgnoreTabIndex, bool aForDocumentNavigation, bool aNavigateByKey, 4238 bool aReachedToEndForDocumentNavigation) { 4239 MOZ_ASSERT(aStartOwner == FindScopeOwner(aStartContent), 4240 "aStartOwner should be the scope owner of aStartContent"); 4241 MOZ_ASSERT(IsScopeOwner(aStartOwner), 4242 "scope owner should be host, slot, or popover"); 4243 4244 nsCOMPtr<nsIContent> owner = aStartOwner; 4245 nsCOMPtr<nsIContent> startContent = aStartContent; 4246 while (IsScopeOwner(owner)) { 4247 int32_t tabIndex = 0; 4248 if (IsHostOrSlot(startContent)) { 4249 tabIndex = HostOrSlotTabIndexValue(startContent); 4250 } else if (nsIFrame* frame = startContent->GetPrimaryFrame()) { 4251 tabIndex = frame->IsFocusable().mTabIndex; 4252 } else { 4253 tabIndex = startContent->IsFocusableWithoutStyle().mTabIndex; 4254 } 4255 nsIContent* contentToFocus = GetNextTabbableContentInScope( 4256 owner, startContent, aOriginalStartContent, aForward, tabIndex, 4257 tabIndex < 0, aForDocumentNavigation, aNavigateByKey, 4258 false /* aSkipOwner */, aReachedToEndForDocumentNavigation); 4259 if (contentToFocus && contentToFocus != aStartContent) { 4260 return contentToFocus; 4261 } 4262 4263 startContent = owner; 4264 owner = FindScopeOwner(startContent); 4265 } 4266 4267 // If not found in shadow DOM, search from the top level shadow host in light 4268 // DOM 4269 aStartContent = startContent; 4270 if (IsHostOrSlot(startContent)) { 4271 *aCurrentTabIndex = HostOrSlotTabIndexValue(startContent); 4272 } else if (nsIFrame* frame = startContent->GetPrimaryFrame()) { 4273 *aCurrentTabIndex = frame->IsFocusable().mTabIndex; 4274 } else { 4275 *aCurrentTabIndex = startContent->IsFocusableWithoutStyle().mTabIndex; 4276 } 4277 4278 if (*aCurrentTabIndex < 0) { 4279 *aIgnoreTabIndex = true; 4280 } 4281 4282 return nullptr; 4283 } 4284 4285 static nsIContent* GetTopLevelScopeOwner(nsIContent* aContent) { 4286 nsIContent* topLevelScopeOwner = nullptr; 4287 while (aContent) { 4288 if (HTMLSlotElement* slot = aContent->GetAssignedSlot()) { 4289 aContent = slot; 4290 topLevelScopeOwner = aContent; 4291 } else if (ShadowRoot* shadowRoot = aContent->GetContainingShadow()) { 4292 aContent = shadowRoot->Host(); 4293 topLevelScopeOwner = aContent; 4294 } else { 4295 aContent = aContent->GetParent(); 4296 if (aContent && (HTMLSlotElement::FromNode(aContent) || 4297 IsOpenPopoverWithInvoker(aContent))) { 4298 topLevelScopeOwner = aContent; 4299 } 4300 } 4301 } 4302 4303 return topLevelScopeOwner; 4304 } 4305 4306 nsresult nsFocusManager::GetNextTabbableContent( 4307 PresShell* aPresShell, nsIContent* aRootContent, 4308 nsIContent* aOriginalStartContent, nsIContent* aStartContent, bool aForward, 4309 int32_t aCurrentTabIndex, bool aIgnoreTabIndex, bool aForDocumentNavigation, 4310 bool aNavigateByKey, bool aSkipPopover, 4311 bool aReachedToEndForDocumentNavigation, nsIContent** aResultContent) { 4312 *aResultContent = nullptr; 4313 4314 if (!aStartContent) { 4315 return NS_OK; 4316 } 4317 4318 nsCOMPtr<nsIContent> startContent = aStartContent; 4319 nsCOMPtr<nsIContent> currentTopLevelScopeOwner = 4320 GetTopLevelScopeOwner(startContent); 4321 4322 LOGCONTENTNAVIGATION("GetNextTabbable: %s", startContent); 4323 LOGFOCUSNAVIGATION((" tabindex: %d", aCurrentTabIndex)); 4324 4325 // If startContent is a shadow host or slot in forward navigation, 4326 // search in scope owned by startContent 4327 if (aForward && IsHostOrSlot(startContent)) { 4328 nsIContent* contentToFocus = GetNextTabbableContentInScope( 4329 startContent, startContent, aOriginalStartContent, aForward, 1, 4330 aIgnoreTabIndex, aForDocumentNavigation, aNavigateByKey, 4331 true /* aSkipOwner */, aReachedToEndForDocumentNavigation); 4332 if (contentToFocus) { 4333 NS_ADDREF(*aResultContent = contentToFocus); 4334 return NS_OK; 4335 } 4336 } 4337 4338 // If startContent is a popover invoker, search the popover scope. 4339 if (!aSkipPopover) { 4340 if (InvokerForPopoverShowingState(startContent)) { 4341 if (aForward) { 4342 RefPtr<nsIContent> popover = 4343 GetAssociatedPopoverFromInvoker(startContent); 4344 nsIContent* contentToFocus = GetNextTabbableContentInScope( 4345 popover, popover, aOriginalStartContent, aForward, 1, 4346 aIgnoreTabIndex, aForDocumentNavigation, aNavigateByKey, 4347 true /* aSkipOwner */, aReachedToEndForDocumentNavigation); 4348 if (contentToFocus) { 4349 NS_ADDREF(*aResultContent = contentToFocus); 4350 return NS_OK; 4351 } 4352 } 4353 } 4354 } 4355 4356 // If startContent is in a scope owned by Shadow DOM or popover, search 4357 // from scope including startContent 4358 if (nsCOMPtr<nsIContent> owner = FindScopeOwner(startContent)) { 4359 nsIContent* contentToFocus = GetNextTabbableContentInAncestorScopes( 4360 owner, startContent /* inout */, aOriginalStartContent, aForward, 4361 &aCurrentTabIndex, &aIgnoreTabIndex, aForDocumentNavigation, 4362 aNavigateByKey, aReachedToEndForDocumentNavigation); 4363 if (contentToFocus) { 4364 // If contentToFocus is itself a popover invoker then a backwards move 4365 // should cycle through the open popovers' content 4366 if (!aForward && InvokerForPopoverShowingState(contentToFocus)) { 4367 RefPtr<nsIContent> popover = 4368 GetAssociatedPopoverFromInvoker(contentToFocus); 4369 nsIContent* popoverContent = GetNextTabbableContentInScope( 4370 popover, popover, aOriginalStartContent, aForward, 0, 4371 aIgnoreTabIndex, aForDocumentNavigation, aNavigateByKey, 4372 true /* aSkipOwner */, aReachedToEndForDocumentNavigation); 4373 if (popoverContent) { 4374 contentToFocus = popoverContent; 4375 } 4376 } 4377 NS_ADDREF(*aResultContent = contentToFocus); 4378 return NS_OK; 4379 } 4380 } 4381 4382 // If we reach here, it means no next tabbable content in shadow DOM or 4383 // popover. We need to continue searching in light DOM, starting at the top 4384 // level shadow host in light DOM (updated startContent) and its tabindex 4385 // (updated aCurrentTabIndex). 4386 MOZ_ASSERT(!FindScopeOwner(startContent), 4387 "startContent should not be owned by Shadow DOM at this point"); 4388 4389 nsPresContext* presContext = aPresShell->GetPresContext(); 4390 4391 bool getNextFrame = true; 4392 nsCOMPtr<nsIContent> iterStartContent = startContent; 4393 nsIContent* topLevelScopeStartContent = startContent; 4394 // Iterate tab index to find corresponding contents 4395 while (1) { 4396 nsIFrame* frame = iterStartContent->GetPrimaryFrame(); 4397 // if there is no frame, look for another content node that has a frame 4398 while (!frame) { 4399 // if the root content doesn't have a frame, just return 4400 if (iterStartContent == aRootContent) { 4401 return NS_OK; 4402 } 4403 4404 // look for the next or previous content node in tree order 4405 iterStartContent = aForward ? iterStartContent->GetNextNode() 4406 : iterStartContent->GetPrevNode(); 4407 if (!iterStartContent) { 4408 break; 4409 } 4410 4411 frame = iterStartContent->GetPrimaryFrame(); 4412 // Host without frame, enter its scope. 4413 if (!frame && iterStartContent->GetShadowRoot()) { 4414 int32_t tabIndex = HostOrSlotTabIndexValue(iterStartContent); 4415 if (tabIndex >= 0 && 4416 (aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) { 4417 nsIContent* contentToFocus = GetNextTabbableContentInScope( 4418 iterStartContent, iterStartContent, aOriginalStartContent, 4419 aForward, aForward ? 1 : 0, aIgnoreTabIndex, 4420 aForDocumentNavigation, aNavigateByKey, true /* aSkipOwner */, 4421 aReachedToEndForDocumentNavigation); 4422 if (contentToFocus) { 4423 NS_ADDREF(*aResultContent = contentToFocus); 4424 return NS_OK; 4425 } 4426 } 4427 } 4428 // we've already skipped over the initial focused content, so we 4429 // don't want to traverse frames. 4430 getNextFrame = false; 4431 } 4432 4433 Maybe<nsFrameIterator> frameIterator; 4434 if (frame) { 4435 // For tab navigation, pass false for aSkipPopupChecks so that we don't 4436 // iterate into or out of a popup. For document naviation pass true to 4437 // ignore these boundaries. 4438 frameIterator.emplace(presContext, frame, nsFrameIterator::Type::PreOrder, 4439 false, // aVisual 4440 false, // aLockInScrollView 4441 true, // aFollowOOFs 4442 aForDocumentNavigation // aSkipPopupChecks 4443 ); 4444 MOZ_ASSERT(frameIterator); 4445 4446 if (iterStartContent == aRootContent) { 4447 if (!aForward) { 4448 frameIterator->Last(); 4449 } else if (aRootContent->IsFocusableWithoutStyle()) { 4450 frameIterator->Next(); 4451 } 4452 frame = frameIterator->CurrentItem(); 4453 } else if (getNextFrame && 4454 (!iterStartContent || 4455 !iterStartContent->IsHTMLElement(nsGkAtoms::area))) { 4456 // Need to do special check in case we're in an imagemap which has 4457 // multiple content nodes per frame, so don't skip over the starting 4458 // frame. 4459 frame = frameIterator->Traverse(aForward); 4460 } 4461 } 4462 4463 nsIContent* oldTopLevelScopeOwner = nullptr; 4464 // Walk frames to find something tabbable matching aCurrentTabIndex 4465 while (frame) { 4466 // Try to find the topmost scope owner, since we want to skip the node 4467 // that is not owned by document in frame traversal. 4468 const nsCOMPtr<nsIContent> currentContent = frame->GetContent(); 4469 if (currentTopLevelScopeOwner) { 4470 oldTopLevelScopeOwner = currentTopLevelScopeOwner; 4471 } 4472 currentTopLevelScopeOwner = GetTopLevelScopeOwner(currentContent); 4473 4474 // We handle popover case separately. 4475 if (currentTopLevelScopeOwner && 4476 currentTopLevelScopeOwner == oldTopLevelScopeOwner && 4477 !IsOpenPopoverWithInvoker(currentTopLevelScopeOwner)) { 4478 // We're within non-document scope, continue. 4479 do { 4480 if (aForward) { 4481 frameIterator->Next(); 4482 } else { 4483 frameIterator->Prev(); 4484 } 4485 frame = frameIterator->CurrentItem(); 4486 // For the usage of GetPrevContinuation, see the comment 4487 // at the end of while (frame) loop. 4488 } while (frame && frame->GetPrevContinuation()); 4489 continue; 4490 } 4491 4492 // Stepping out popover scope. 4493 // For forward, search for the next tabbable content after invoker. 4494 // For backward, we should get back to the invoker if the invoker is 4495 // focusable. Otherwise search for the next tabbable content after 4496 // invoker. 4497 if (oldTopLevelScopeOwner && 4498 IsOpenPopoverWithInvoker(oldTopLevelScopeOwner) && 4499 currentTopLevelScopeOwner != oldTopLevelScopeOwner) { 4500 auto* popover = oldTopLevelScopeOwner->AsElement(); 4501 RefPtr<Element> invoker = popover->GetPopoverData()->GetInvoker(); 4502 MOZ_ASSERT(invoker, "IsOpenPopoverWithInvoker guarantees this"); 4503 RefPtr<Element> rootElement = invoker; 4504 if (auto* doc = invoker->GetComposedDoc()) { 4505 rootElement = doc->GetRootElement(); 4506 } 4507 if (aForward) { 4508 if (nsIFrame* frame = invoker->GetPrimaryFrame()) { 4509 int32_t tabIndex = frame->IsFocusable().mTabIndex; 4510 if (tabIndex >= 0 && 4511 (aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) { 4512 nsresult rv = GetNextTabbableContent( 4513 aPresShell, rootElement, nullptr, invoker, true, tabIndex, 4514 false, false, aNavigateByKey, true, 4515 aReachedToEndForDocumentNavigation, aResultContent); 4516 if (NS_SUCCEEDED(rv) && *aResultContent) { 4517 return rv; 4518 } 4519 } 4520 } 4521 } else if (invoker) { 4522 nsIFrame* frame = invoker->GetPrimaryFrame(); 4523 if (frame && frame->IsFocusable()) { 4524 invoker.forget(aResultContent); 4525 return NS_OK; 4526 } 4527 nsresult rv = GetNextTabbableContent( 4528 aPresShell, rootElement, aOriginalStartContent, invoker, false, 0, 4529 true, false, aNavigateByKey, true, 4530 aReachedToEndForDocumentNavigation, aResultContent); 4531 if (NS_SUCCEEDED(rv) && *aResultContent) { 4532 return rv; 4533 } 4534 } 4535 } 4536 4537 if (!aForward && InvokerForPopoverShowingState(currentContent)) { 4538 int32_t tabIndex = frame->IsFocusable().mTabIndex; 4539 if (tabIndex >= 0 && 4540 (aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) { 4541 RefPtr<nsIContent> popover = 4542 GetAssociatedPopoverFromInvoker(currentContent); 4543 nsIContent* contentToFocus = GetNextTabbableContentInScope( 4544 popover, popover, aOriginalStartContent, aForward, 0, 4545 aIgnoreTabIndex, aForDocumentNavigation, aNavigateByKey, 4546 true /* aSkipOwner */, aReachedToEndForDocumentNavigation); 4547 4548 if (contentToFocus) { 4549 NS_ADDREF(*aResultContent = contentToFocus); 4550 return NS_OK; 4551 } 4552 } 4553 } 4554 // For document navigation, check if this element is an open panel. Since 4555 // panels aren't focusable (tabIndex would be -1), we'll just assume that 4556 // for document navigation, the tabIndex is 0. 4557 if (aForDocumentNavigation && currentContent && (aCurrentTabIndex == 0) && 4558 currentContent->IsXULElement(nsGkAtoms::panel)) { 4559 nsMenuPopupFrame* popupFrame = do_QueryFrame(frame); 4560 // Check if the panel is open. Closed panels are ignored since you can't 4561 // focus anything in them. 4562 if (popupFrame && popupFrame->IsOpen()) { 4563 // When moving backward, skip the popup we started in otherwise it 4564 // will be selected again. 4565 bool validPopup = true; 4566 if (!aForward) { 4567 nsIContent* content = topLevelScopeStartContent; 4568 while (content) { 4569 if (content == currentContent) { 4570 validPopup = false; 4571 break; 4572 } 4573 4574 content = content->GetParent(); 4575 } 4576 } 4577 4578 if (validPopup) { 4579 // Since a panel isn't focusable itself, find the first focusable 4580 // content within the popup. If there isn't any focusable content 4581 // in the popup, skip this popup and continue iterating through the 4582 // frames. We pass the panel itself (currentContent) as the starting 4583 // and root content, so that we only find content within the panel. 4584 // Note also that we pass false for aForDocumentNavigation since we 4585 // want to locate the first content, not the first document. 4586 nsresult rv = GetNextTabbableContent( 4587 aPresShell, currentContent, nullptr, currentContent, true, 1, 4588 false, false, aNavigateByKey, false, 4589 aReachedToEndForDocumentNavigation, aResultContent); 4590 if (NS_SUCCEEDED(rv) && *aResultContent) { 4591 return rv; 4592 } 4593 } 4594 } 4595 } 4596 4597 // As of now, 2018/04/12, sequential focus navigation is still 4598 // in the obsolete Shadow DOM specification. 4599 // http://w3c.github.io/webcomponents/spec/shadow/#sequential-focus-navigation 4600 // "if ELEMENT is focusable, a shadow host, or a slot element, 4601 // append ELEMENT to NAVIGATION-ORDER." 4602 // and later in "For each element ELEMENT in NAVIGATION-ORDER: " 4603 // hosts and slots are handled before other elements. 4604 if (currentTopLevelScopeOwner && 4605 !IsOpenPopoverWithInvoker(currentTopLevelScopeOwner)) { 4606 bool focusableHostSlot; 4607 int32_t tabIndex = HostOrSlotTabIndexValue(currentTopLevelScopeOwner, 4608 &focusableHostSlot); 4609 // Host or slot itself isn't focusable or going backwards, enter its 4610 // scope. 4611 if ((!aForward || !focusableHostSlot) && tabIndex >= 0 && 4612 (aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) { 4613 nsIContent* contentToFocus = GetNextTabbableContentInScope( 4614 currentTopLevelScopeOwner, currentTopLevelScopeOwner, 4615 aOriginalStartContent, aForward, aForward ? 1 : 0, 4616 aIgnoreTabIndex, aForDocumentNavigation, aNavigateByKey, 4617 true /* aSkipOwner */, aReachedToEndForDocumentNavigation); 4618 if (contentToFocus) { 4619 NS_ADDREF(*aResultContent = contentToFocus); 4620 return NS_OK; 4621 } 4622 // If we've wrapped around already, then carry on. 4623 if (aOriginalStartContent && 4624 currentTopLevelScopeOwner == 4625 GetTopLevelScopeOwner(aOriginalStartContent)) { 4626 // FIXME: Shouldn't this return null instead? aOriginalStartContent 4627 // isn't focusable after all. 4628 NS_ADDREF(*aResultContent = aOriginalStartContent); 4629 return NS_OK; 4630 } 4631 } 4632 // There is no next tabbable content in currentTopLevelScopeOwner's 4633 // scope. We should continue the loop in order to skip all contents that 4634 // is in currentTopLevelScopeOwner's scope. 4635 continue; 4636 } 4637 4638 MOZ_ASSERT( 4639 !GetTopLevelScopeOwner(currentContent) || 4640 IsOpenPopoverWithInvoker(GetTopLevelScopeOwner(currentContent)), 4641 "currentContent should be in top-level-scope at this point unless " 4642 "for popover case"); 4643 4644 // TabIndex not set defaults to 0 for form elements, anchors and other 4645 // elements that are normally focusable. Tabindex defaults to -1 4646 // for elements that are not normally focusable. 4647 // The returned computed tabindex from IsFocusable() is as follows: 4648 // clang-format off 4649 // < 0 not tabbable at all 4650 // == 0 in normal tab order (last after positive tabindexed items) 4651 // > 0 can be tabbed to in the order specified by this value 4652 // clang-format on 4653 int32_t tabIndex = frame->IsFocusable().mTabIndex; 4654 4655 LOGCONTENTNAVIGATION("Next Tabbable %s:", frame->GetContent()); 4656 LOGFOCUSNAVIGATION( 4657 (" with tabindex: %d expected: %d", tabIndex, aCurrentTabIndex)); 4658 4659 if (tabIndex >= 0) { 4660 NS_ASSERTION(currentContent, 4661 "IsFocusable set a tabindex for a frame with no content"); 4662 if (!aForDocumentNavigation && 4663 currentContent->IsHTMLElement(nsGkAtoms::img) && 4664 currentContent->AsElement()->HasAttr(nsGkAtoms::usemap)) { 4665 // This is an image with a map. Image map areas are not traversed by 4666 // nsFrameIterator so look for the next or previous area element. 4667 nsIContent* areaContent = GetNextTabbableMapArea( 4668 aForward, aCurrentTabIndex, currentContent->AsElement(), 4669 iterStartContent); 4670 if (areaContent) { 4671 NS_ADDREF(*aResultContent = areaContent); 4672 return NS_OK; 4673 } 4674 } else if (aIgnoreTabIndex || aCurrentTabIndex == tabIndex) { 4675 // break out if we've wrapped around to the start again. 4676 if (aOriginalStartContent && 4677 currentContent == aOriginalStartContent) { 4678 NS_ADDREF(*aResultContent = currentContent); 4679 return NS_OK; 4680 } 4681 4682 // If this is a remote child browser, call NavigateDocument to have 4683 // the child process continue the navigation. Return a special error 4684 // code to have the caller return early. If the child ends up not 4685 // being focusable in some way, the child process will call back 4686 // into document navigation again by calling MoveFocus. 4687 if (BrowserParent* remote = BrowserParent::GetFrom(currentContent)) { 4688 if (aNavigateByKey) { 4689 remote->NavigateByKey(aForward, aForDocumentNavigation); 4690 return NS_SUCCESS_DOM_NO_OPERATION; 4691 } 4692 return NS_OK; 4693 } 4694 4695 // Same as above but for out-of-process iframes 4696 if (auto* bbc = BrowserBridgeChild::GetFrom(currentContent)) { 4697 if (aNavigateByKey) { 4698 bbc->NavigateByKey(aForward, aForDocumentNavigation); 4699 return NS_SUCCESS_DOM_NO_OPERATION; 4700 } 4701 return NS_OK; 4702 } 4703 4704 // Next, for document navigation, check if this a non-remote child 4705 // document. 4706 bool checkSubDocument = true; 4707 if (aForDocumentNavigation && 4708 TryDocumentNavigation(currentContent, &checkSubDocument, 4709 aResultContent)) { 4710 return NS_OK; 4711 } 4712 4713 if (checkSubDocument) { 4714 // found a node with a matching tab index. Check if it is a child 4715 // frame. If so, navigate into the child frame instead. 4716 if (TryToMoveFocusToSubDocument( 4717 currentContent, aOriginalStartContent, aForward, 4718 aForDocumentNavigation, aNavigateByKey, 4719 aReachedToEndForDocumentNavigation, aResultContent)) { 4720 MOZ_ASSERT(*aResultContent); 4721 return NS_OK; 4722 } 4723 // otherwise, use this as the next content node to tab to, unless 4724 // this was the element we started on. This would happen for 4725 // instance on an element with child frames, where frame navigation 4726 // could return the original element again. In that case, just skip 4727 // it. Also, if the next content node is the root content, then 4728 // return it. This latter case would happen only if someone made a 4729 // popup focusable. 4730 else if (currentContent == aRootContent || 4731 currentContent != startContent) { 4732 NS_ADDREF(*aResultContent = currentContent); 4733 return NS_OK; 4734 } 4735 } else if (currentContent && aReachedToEndForDocumentNavigation && 4736 nsContentUtils::IsChromeDoc( 4737 currentContent->GetComposedDoc())) { 4738 // aReachedToEndForDocumentNavigation is true means 4739 // 1. This is a document navigation (i.e, VK_F6, Control + Tab) 4740 // 2. This is the top-level document (Note that we may start from 4741 // a subdocument) 4742 // 3. We've searched through the this top-level document already 4743 if (!GetRootForChildDocument(currentContent)) { 4744 // We'd like to focus the first focusable element of this 4745 // top-level chrome document. 4746 if (currentContent == aRootContent || 4747 currentContent != startContent) { 4748 NS_ADDREF(*aResultContent = currentContent); 4749 return NS_OK; 4750 } 4751 } 4752 } 4753 } 4754 } else if (aOriginalStartContent && 4755 currentContent == aOriginalStartContent) { 4756 // not focusable, so return if we have wrapped around to the original 4757 // content. This is necessary in case the original starting content was 4758 // not focusable. 4759 // 4760 // FIXME: Shouldn't this return null instead? currentContent isn't 4761 // focusable after all. 4762 NS_ADDREF(*aResultContent = currentContent); 4763 return NS_OK; 4764 } 4765 4766 // Move to the next or previous frame, but ignore continuation frames 4767 // since only the first frame should be involved in focusability. 4768 // Otherwise, a loop will occur in the following example: 4769 // <span tabindex="1">...<a/><a/>...</span> 4770 // where the text wraps onto multiple lines. Tabbing from the second 4771 // link can find one of the span's continuation frames between the link 4772 // and the end of the span, and the span would end up getting focused 4773 // again. 4774 do { 4775 if (aForward) { 4776 frameIterator->Next(); 4777 } else { 4778 frameIterator->Prev(); 4779 } 4780 frame = frameIterator->CurrentItem(); 4781 } while (frame && frame->GetPrevContinuation()); 4782 } 4783 4784 // If already at lowest priority tab (0), end search completely. 4785 // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0 4786 if (aCurrentTabIndex == (aForward ? 0 : 1)) { 4787 break; 4788 } 4789 4790 // continue looking for next highest priority tabindex 4791 aCurrentTabIndex = 4792 GetNextTabIndex(aRootContent, aCurrentTabIndex, aForward); 4793 startContent = iterStartContent = aRootContent; 4794 currentTopLevelScopeOwner = GetTopLevelScopeOwner(startContent); 4795 } 4796 4797 return NS_OK; 4798 } 4799 4800 bool nsFocusManager::TryDocumentNavigation(nsIContent* aCurrentContent, 4801 bool* aCheckSubDocument, 4802 nsIContent** aResultContent) { 4803 *aCheckSubDocument = true; 4804 if (RefPtr<Element> rootElementForChildDocument = 4805 GetRootForChildDocument(aCurrentContent)) { 4806 // If GetRootForChildDocument returned something then call 4807 // FocusFirst to find the root or first element to focus within 4808 // the child document. If this is a frameset though, skip this and 4809 // fall through to normal tab navigation to iterate into 4810 // the frameset's frames and locate the first focusable frame. 4811 if (!rootElementForChildDocument->IsHTMLElement(nsGkAtoms::frameset)) { 4812 *aCheckSubDocument = false; 4813 (void)FocusFirst(rootElementForChildDocument, aResultContent, 4814 false /* aReachedToEndForDocumentNavigation */); 4815 return *aResultContent != nullptr; 4816 } 4817 } else { 4818 // Set aCheckSubDocument to false, as this was neither a frame 4819 // type element or a child document that was focusable. 4820 *aCheckSubDocument = false; 4821 } 4822 4823 return false; 4824 } 4825 4826 bool nsFocusManager::TryToMoveFocusToSubDocument( 4827 nsIContent* aCurrentContent, nsIContent* aOriginalStartContent, 4828 bool aForward, bool aForDocumentNavigation, bool aNavigateByKey, 4829 bool aReachedToEndForDocumentNavigation, nsIContent** aResultContent) { 4830 Document* doc = aCurrentContent->GetComposedDoc(); 4831 NS_ASSERTION(doc, "content not in document"); 4832 Document* subdoc = doc->GetSubDocumentFor(aCurrentContent); 4833 if (subdoc && !subdoc->EventHandlingSuppressed()) { 4834 if (RefPtr<Element> rootElement = subdoc->GetRootElement()) { 4835 if (RefPtr<PresShell> subPresShell = subdoc->GetPresShell()) { 4836 nsresult rv = GetNextTabbableContent( 4837 subPresShell, rootElement, aOriginalStartContent, rootElement, 4838 aForward, (aForward ? 1 : 0), false, aForDocumentNavigation, 4839 aNavigateByKey, false, aReachedToEndForDocumentNavigation, 4840 aResultContent); 4841 NS_ENSURE_SUCCESS(rv, false); 4842 if (*aResultContent) { 4843 return true; 4844 } 4845 if (rootElement->IsEditable()) { 4846 // Only move to the root element with a valid reason 4847 *aResultContent = rootElement; 4848 NS_ADDREF(*aResultContent); 4849 return true; 4850 } 4851 } 4852 } 4853 } 4854 return false; 4855 } 4856 4857 nsIContent* nsFocusManager::GetNextTabbableMapArea(bool aForward, 4858 int32_t aCurrentTabIndex, 4859 Element* aImageContent, 4860 nsIContent* aStartContent) { 4861 if (aImageContent->IsInComposedDoc()) { 4862 HTMLImageElement* imgElement = HTMLImageElement::FromNode(aImageContent); 4863 // The caller should check the element type, so we can assert here. 4864 MOZ_ASSERT(imgElement); 4865 4866 nsCOMPtr<nsIContent> mapContent = imgElement->FindImageMap(); 4867 if (!mapContent) { 4868 return nullptr; 4869 } 4870 // First see if the the start content is in this map 4871 Maybe<uint32_t> indexOfStartContent = 4872 mapContent->ComputeIndexOf(aStartContent); 4873 nsIContent* scanStartContent; 4874 Focusable focusable; 4875 if (indexOfStartContent.isNothing() || 4876 ((focusable = aStartContent->IsFocusableWithoutStyle()) && 4877 focusable.mTabIndex != aCurrentTabIndex)) { 4878 // If aStartContent is in this map we must start iterating past it. 4879 // We skip the case where aStartContent has tabindex == aStartContent 4880 // since the next tab ordered element might be before it 4881 // (or after for backwards) in the child list. 4882 scanStartContent = 4883 aForward ? mapContent->GetFirstChild() : mapContent->GetLastChild(); 4884 } else { 4885 scanStartContent = aForward ? aStartContent->GetNextSibling() 4886 : aStartContent->GetPreviousSibling(); 4887 } 4888 4889 for (nsCOMPtr<nsIContent> areaContent = scanStartContent; areaContent; 4890 areaContent = aForward ? areaContent->GetNextSibling() 4891 : areaContent->GetPreviousSibling()) { 4892 focusable = areaContent->IsFocusableWithoutStyle(); 4893 if (focusable && focusable.mTabIndex == aCurrentTabIndex) { 4894 return areaContent; 4895 } 4896 } 4897 } 4898 4899 return nullptr; 4900 } 4901 4902 int32_t nsFocusManager::GetNextTabIndex(nsIContent* aParent, 4903 int32_t aCurrentTabIndex, 4904 bool aForward) { 4905 int32_t tabIndex, childTabIndex; 4906 StyleChildrenIterator iter(aParent); 4907 4908 if (aForward) { 4909 tabIndex = 0; 4910 for (nsIContent* child = iter.GetNextChild(); child; 4911 child = iter.GetNextChild()) { 4912 // Skip child's descendants if child is a shadow host or slot, as they are 4913 // in the focus navigation scope owned by child's shadow root 4914 if (!IsHostOrSlot(child)) { 4915 childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward); 4916 if (childTabIndex > aCurrentTabIndex && childTabIndex != tabIndex) { 4917 tabIndex = (tabIndex == 0 || childTabIndex < tabIndex) ? childTabIndex 4918 : tabIndex; 4919 } 4920 } 4921 4922 nsAutoString tabIndexStr; 4923 if (child->IsElement()) { 4924 child->AsElement()->GetAttr(nsGkAtoms::tabindex, tabIndexStr); 4925 } 4926 nsresult ec; 4927 int32_t val = tabIndexStr.ToInteger(&ec); 4928 if (NS_SUCCEEDED(ec) && val > aCurrentTabIndex && val != tabIndex) { 4929 tabIndex = (tabIndex == 0 || val < tabIndex) ? val : tabIndex; 4930 } 4931 } 4932 } else { /* !aForward */ 4933 tabIndex = 1; 4934 for (nsIContent* child = iter.GetNextChild(); child; 4935 child = iter.GetNextChild()) { 4936 // Skip child's descendants if child is a shadow host or slot, as they are 4937 // in the focus navigation scope owned by child's shadow root 4938 if (!IsHostOrSlot(child)) { 4939 childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward); 4940 if ((aCurrentTabIndex == 0 && childTabIndex > tabIndex) || 4941 (childTabIndex < aCurrentTabIndex && childTabIndex > tabIndex)) { 4942 tabIndex = childTabIndex; 4943 } 4944 } 4945 4946 nsAutoString tabIndexStr; 4947 if (child->IsElement()) { 4948 child->AsElement()->GetAttr(nsGkAtoms::tabindex, tabIndexStr); 4949 } 4950 nsresult ec; 4951 int32_t val = tabIndexStr.ToInteger(&ec); 4952 if (NS_SUCCEEDED(ec)) { 4953 if ((aCurrentTabIndex == 0 && val > tabIndex) || 4954 (val < aCurrentTabIndex && val > tabIndex)) { 4955 tabIndex = val; 4956 } 4957 } 4958 } 4959 } 4960 4961 return tabIndex; 4962 } 4963 4964 nsresult nsFocusManager::FocusFirst(Element* aRootElement, 4965 nsIContent** aNextContent, 4966 bool aReachedToEndForDocumentNavigation) { 4967 if (!aRootElement) { 4968 return NS_OK; 4969 } 4970 4971 Document* doc = aRootElement->GetComposedDoc(); 4972 if (doc) { 4973 if (nsContentUtils::IsChromeDoc(doc)) { 4974 // If the redirectdocumentfocus attribute is set, redirect the focus to a 4975 // specific element. This is primarily used to retarget the focus to the 4976 // urlbar during document navigation. 4977 nsAutoString retarget; 4978 4979 if (aRootElement->GetAttr(nsGkAtoms::retargetdocumentfocus, retarget)) { 4980 RefPtr<Element> element = doc->GetElementById(retarget); 4981 nsCOMPtr<nsIContent> retargetElement = 4982 FlushAndCheckIfFocusable(element, 0); 4983 if (retargetElement) { 4984 retargetElement.forget(aNextContent); 4985 return NS_OK; 4986 } 4987 } 4988 } 4989 4990 nsCOMPtr<nsIDocShell> docShell = doc->GetDocShell(); 4991 if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) { 4992 // If the found content is in a chrome shell, navigate forward one 4993 // tabbable item so that the first item is focused. Note that we 4994 // always go forward and not back here. 4995 if (RefPtr<PresShell> presShell = doc->GetPresShell()) { 4996 return GetNextTabbableContent( 4997 presShell, aRootElement, nullptr, aRootElement, true, 1, false, 4998 aReachedToEndForDocumentNavigation, true, false, 4999 aReachedToEndForDocumentNavigation, aNextContent); 5000 } 5001 } 5002 } 5003 5004 NS_ADDREF(*aNextContent = aRootElement); 5005 return NS_OK; 5006 } 5007 5008 Element* nsFocusManager::GetRootForFocus(nsPIDOMWindowOuter* aWindow, 5009 Document* aDocument, 5010 bool aForDocumentNavigation, 5011 bool aCheckVisibility) { 5012 if (!aForDocumentNavigation) { 5013 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell(); 5014 if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) { 5015 return nullptr; 5016 } 5017 } 5018 5019 if (aCheckVisibility && !IsWindowVisible(aWindow)) return nullptr; 5020 5021 // If the body is contenteditable, use the editor's root element rather than 5022 // the actual root element. 5023 RefPtr<Element> rootElement = 5024 nsLayoutUtils::GetEditableRootContentByContentEditable(aDocument); 5025 if (!rootElement || !rootElement->GetPrimaryFrame()) { 5026 rootElement = aDocument->GetRootElement(); 5027 if (!rootElement) { 5028 return nullptr; 5029 } 5030 } 5031 5032 if (aCheckVisibility && !rootElement->GetPrimaryFrame()) { 5033 return nullptr; 5034 } 5035 5036 // Finally, check if this is a frameset 5037 if (aDocument && aDocument->IsHTMLOrXHTML()) { 5038 Element* htmlChild = aDocument->GetHtmlChildElement(nsGkAtoms::frameset); 5039 if (htmlChild) { 5040 // In document navigation mode, return the frameset so that navigation 5041 // descends into the child frames. 5042 return aForDocumentNavigation ? htmlChild : nullptr; 5043 } 5044 } 5045 5046 return rootElement; 5047 } 5048 5049 Element* nsFocusManager::GetRootForChildDocument(nsIContent* aContent) { 5050 // Check for elements that represent child documents, that is, browsers, 5051 // editors or frames from a frameset. We don't include iframes since we 5052 // consider them to be an integral part of the same window or page. 5053 if (!aContent || !(aContent->IsXULElement(nsGkAtoms::browser) || 5054 aContent->IsXULElement(nsGkAtoms::editor) || 5055 aContent->IsHTMLElement(nsGkAtoms::frame))) { 5056 return nullptr; 5057 } 5058 5059 Document* doc = aContent->GetComposedDoc(); 5060 if (!doc) { 5061 return nullptr; 5062 } 5063 5064 Document* subdoc = doc->GetSubDocumentFor(aContent); 5065 if (!subdoc || subdoc->EventHandlingSuppressed()) { 5066 return nullptr; 5067 } 5068 5069 nsCOMPtr<nsPIDOMWindowOuter> window = subdoc->GetWindow(); 5070 return GetRootForFocus(window, subdoc, true, true); 5071 } 5072 5073 static bool IsLink(nsIContent* aContent) { 5074 return aContent->IsElement() && aContent->AsElement()->IsLink(); 5075 } 5076 5077 void nsFocusManager::GetFocusInSelection(nsPIDOMWindowOuter* aWindow, 5078 nsIContent* aStartSelection, 5079 nsIContent* aEndSelection, 5080 nsIContent** aFocusedContent) { 5081 *aFocusedContent = nullptr; 5082 5083 nsCOMPtr<nsIContent> testContent = aStartSelection; 5084 nsCOMPtr<nsIContent> nextTestContent = aEndSelection; 5085 5086 nsCOMPtr<nsIContent> currentFocus = aWindow->GetFocusedElement(); 5087 5088 // We now have the correct start node in selectionContent! 5089 // Search for focusable elements, starting with selectionContent 5090 5091 // Method #1: Keep going up while we look - an ancestor might be focusable 5092 // We could end the loop earlier, such as when we're no longer 5093 // in the same frame, by comparing selectionContent->GetPrimaryFrame() 5094 // with a variable holding the starting selectionContent 5095 while (testContent) { 5096 // Keep testing while selectionContent is equal to something, 5097 // eventually we'll run out of ancestors 5098 5099 if (testContent == currentFocus || IsLink(testContent)) { 5100 testContent.forget(aFocusedContent); 5101 return; 5102 } 5103 5104 // Get the parent 5105 testContent = testContent->GetParent(); 5106 5107 if (!testContent) { 5108 // We run this loop again, checking the ancestor chain of the selection's 5109 // end point 5110 testContent = nextTestContent; 5111 nextTestContent = nullptr; 5112 } 5113 } 5114 5115 // We couldn't find an anchor that was an ancestor of the selection start 5116 // Method #2: look for anchor in selection's primary range (depth first 5117 // search) 5118 5119 nsCOMPtr<nsIContent> selectionNode = aStartSelection; 5120 nsCOMPtr<nsIContent> endSelectionNode = aEndSelection; 5121 nsCOMPtr<nsIContent> testNode; 5122 5123 do { 5124 testContent = selectionNode; 5125 5126 // We're looking for any focusable link that could be part of the 5127 // main document's selection. 5128 if (testContent == currentFocus || IsLink(testContent)) { 5129 testContent.forget(aFocusedContent); 5130 return; 5131 } 5132 5133 nsIContent* testNode = selectionNode->GetFirstChild(); 5134 if (testNode) { 5135 selectionNode = testNode; 5136 continue; 5137 } 5138 5139 if (selectionNode == endSelectionNode) { 5140 break; 5141 } 5142 testNode = selectionNode->GetNextSibling(); 5143 if (testNode) { 5144 selectionNode = testNode; 5145 continue; 5146 } 5147 5148 do { 5149 // GetParent is OK here, instead of GetParentNode, because the only case 5150 // where the latter returns something different from the former is when 5151 // GetParentNode is the document. But in that case we would simply get 5152 // null for selectionNode when setting it to testNode->GetNextSibling() 5153 // (because a document has no next sibling). And then the next iteration 5154 // of this loop would get null for GetParentNode anyway, and break out of 5155 // all the loops. 5156 testNode = selectionNode->GetParent(); 5157 if (!testNode || testNode == endSelectionNode) { 5158 selectionNode = nullptr; 5159 break; 5160 } 5161 selectionNode = testNode->GetNextSibling(); 5162 if (selectionNode) { 5163 break; 5164 } 5165 selectionNode = testNode; 5166 } while (true); 5167 } while (selectionNode && selectionNode != endSelectionNode); 5168 } 5169 5170 static void MaybeUnlockPointer(BrowsingContext* aCurrentFocusedContext) { 5171 if (!PointerLockManager::IsInLockContext(aCurrentFocusedContext)) { 5172 PointerLockManager::Unlock("FocusChange"); 5173 } 5174 } 5175 5176 class PointerUnlocker : public Runnable { 5177 public: 5178 PointerUnlocker() : mozilla::Runnable("PointerUnlocker") { 5179 MOZ_ASSERT(XRE_IsParentProcess()); 5180 MOZ_ASSERT(!PointerUnlocker::sActiveUnlocker); 5181 PointerUnlocker::sActiveUnlocker = this; 5182 } 5183 5184 ~PointerUnlocker() { 5185 if (PointerUnlocker::sActiveUnlocker == this) { 5186 PointerUnlocker::sActiveUnlocker = nullptr; 5187 } 5188 } 5189 5190 NS_IMETHOD Run() override { 5191 if (PointerUnlocker::sActiveUnlocker == this) { 5192 PointerUnlocker::sActiveUnlocker = nullptr; 5193 } 5194 NS_ENSURE_STATE(nsFocusManager::GetFocusManager()); 5195 nsPIDOMWindowOuter* focused = 5196 nsFocusManager::GetFocusManager()->GetFocusedWindow(); 5197 MaybeUnlockPointer(focused ? focused->GetBrowsingContext() : nullptr); 5198 return NS_OK; 5199 } 5200 5201 static PointerUnlocker* sActiveUnlocker; 5202 }; 5203 5204 PointerUnlocker* PointerUnlocker::sActiveUnlocker = nullptr; 5205 5206 void nsFocusManager::SetFocusedBrowsingContext(BrowsingContext* aContext, 5207 uint64_t aActionId) { 5208 if (XRE_IsParentProcess()) { 5209 return; 5210 } 5211 MOZ_ASSERT(!ActionIdComparableAndLower( 5212 aActionId, mActionIdForFocusedBrowsingContextInContent)); 5213 mFocusedBrowsingContextInContent = aContext; 5214 mActionIdForFocusedBrowsingContextInContent = aActionId; 5215 if (aContext) { 5216 // We don't send the unset but instead expect the set from 5217 // elsewhere to take care of it. XXX Is that bad? 5218 MOZ_ASSERT(aContext->IsInProcess()); 5219 mozilla::dom::ContentChild* contentChild = 5220 mozilla::dom::ContentChild::GetSingleton(); 5221 MOZ_ASSERT(contentChild); 5222 contentChild->SendSetFocusedBrowsingContext(aContext, aActionId); 5223 } 5224 } 5225 5226 void nsFocusManager::SetFocusedBrowsingContextFromOtherProcess( 5227 BrowsingContext* aContext, uint64_t aActionId) { 5228 MOZ_ASSERT(!XRE_IsParentProcess()); 5229 MOZ_ASSERT(aContext); 5230 if (ActionIdComparableAndLower(aActionId, 5231 mActionIdForFocusedBrowsingContextInContent)) { 5232 // Unclear if this ever happens. 5233 LOGFOCUS( 5234 ("Ignored an attempt to set an in-process BrowsingContext [%p] as " 5235 "focused from another process due to stale action id %" PRIu64 ".", 5236 aContext, aActionId)); 5237 return; 5238 } 5239 if (aContext->IsInProcess()) { 5240 // This message has been in transit for long enough that 5241 // the process association of aContext has changed since 5242 // the other content process sent the message, because 5243 // an iframe in that process became an out-of-process 5244 // iframe while the IPC broadcast that we're receiving 5245 // was in-flight. Let's just ignore this. 5246 LOGFOCUS( 5247 ("Ignored an attempt to set an in-process BrowsingContext [%p] as " 5248 "focused from another process, actionid: %" PRIu64 ".", 5249 aContext, aActionId)); 5250 return; 5251 } 5252 mFocusedBrowsingContextInContent = aContext; 5253 mActionIdForFocusedBrowsingContextInContent = aActionId; 5254 mFocusedElement = nullptr; 5255 mFocusedWindow = nullptr; 5256 } 5257 5258 bool nsFocusManager::SetFocusedBrowsingContextInChrome( 5259 mozilla::dom::BrowsingContext* aContext, uint64_t aActionId) { 5260 MOZ_ASSERT(aActionId); 5261 if (ProcessPendingFocusedBrowsingContextActionId(aActionId)) { 5262 MOZ_DIAGNOSTIC_ASSERT(!ActionIdComparableAndLower( 5263 aActionId, mActionIdForFocusedBrowsingContextInChrome)); 5264 mFocusedBrowsingContextInChrome = aContext; 5265 mActionIdForFocusedBrowsingContextInChrome = aActionId; 5266 return true; 5267 } 5268 return false; 5269 } 5270 5271 BrowsingContext* nsFocusManager::GetFocusedBrowsingContextInChrome() { 5272 return mFocusedBrowsingContextInChrome; 5273 } 5274 5275 void nsFocusManager::BrowsingContextDetached(BrowsingContext* aContext) { 5276 if (mFocusedBrowsingContextInChrome == aContext) { 5277 mFocusedBrowsingContextInChrome = nullptr; 5278 // Deliberately not adjusting the corresponding action id, because 5279 // we don't want changes from the past to take effect. 5280 } 5281 if (mActiveBrowsingContextInChrome == aContext) { 5282 mActiveBrowsingContextInChrome = nullptr; 5283 // Deliberately not adjusting the corresponding action id, because 5284 // we don't want changes from the past to take effect. 5285 } 5286 } 5287 5288 void nsFocusManager::SetActiveBrowsingContextInContent( 5289 mozilla::dom::BrowsingContext* aContext, uint64_t aActionId, 5290 bool aIsEnteringBFCache) { 5291 MOZ_ASSERT(!XRE_IsParentProcess()); 5292 MOZ_ASSERT(!aContext || aContext->IsInProcess()); 5293 mozilla::dom::ContentChild* contentChild = 5294 mozilla::dom::ContentChild::GetSingleton(); 5295 MOZ_ASSERT(contentChild); 5296 5297 if (ActionIdComparableAndLower(aActionId, 5298 mActionIdForActiveBrowsingContextInContent)) { 5299 LOGFOCUS( 5300 ("Ignored an attempt to set an in-process BrowsingContext [%p] as " 5301 "the active browsing context due to a stale action id %" PRIu64 ".", 5302 aContext, aActionId)); 5303 return; 5304 } 5305 5306 if (aContext != mActiveBrowsingContextInContent) { 5307 if (aContext) { 5308 contentChild->SendSetActiveBrowsingContext(aContext, aActionId); 5309 } else if (mActiveBrowsingContextInContent && 5310 !(BFCacheInParent() && aIsEnteringBFCache)) { 5311 // No need to tell the parent process to update the active browsing 5312 // context to null if we are entering BFCache, because the browsing 5313 // context that is about to show will update it. 5314 // 5315 // We want to sync this over only if this isn't happening 5316 // due to the active BrowsingContext switching processes, 5317 // in which case the BrowserChild has already marked itself 5318 // as destroying. 5319 nsPIDOMWindowOuter* outer = 5320 mActiveBrowsingContextInContent->GetDOMWindow(); 5321 if (outer) { 5322 nsPIDOMWindowInner* inner = outer->GetCurrentInnerWindow(); 5323 if (inner) { 5324 WindowGlobalChild* globalChild = inner->GetWindowGlobalChild(); 5325 if (globalChild) { 5326 RefPtr<BrowserChild> browserChild = globalChild->GetBrowserChild(); 5327 if (browserChild && !browserChild->IsDestroyed()) { 5328 contentChild->SendUnsetActiveBrowsingContext( 5329 mActiveBrowsingContextInContent, aActionId); 5330 } 5331 } 5332 } 5333 } 5334 } 5335 } 5336 mActiveBrowsingContextInContentSetFromOtherProcess = false; 5337 mActiveBrowsingContextInContent = aContext; 5338 mActionIdForActiveBrowsingContextInContent = aActionId; 5339 MaybeUnlockPointer(aContext); 5340 } 5341 5342 void nsFocusManager::SetActiveBrowsingContextFromOtherProcess( 5343 BrowsingContext* aContext, uint64_t aActionId) { 5344 MOZ_ASSERT(!XRE_IsParentProcess()); 5345 MOZ_ASSERT(aContext); 5346 if (ActionIdComparableAndLower(aActionId, 5347 mActionIdForActiveBrowsingContextInContent)) { 5348 LOGFOCUS( 5349 ("Ignored an attempt to set active BrowsingContext [%p] from " 5350 "another process due to a stale action id %" PRIu64 ".", 5351 aContext, aActionId)); 5352 return; 5353 } 5354 if (aContext->IsInProcess()) { 5355 // This message has been in transit for long enough that 5356 // the process association of aContext has changed since 5357 // the other content process sent the message, because 5358 // an iframe in that process became an out-of-process 5359 // iframe while the IPC broadcast that we're receiving 5360 // was in-flight. Let's just ignore this. 5361 LOGFOCUS( 5362 ("Ignored an attempt to set an in-process BrowsingContext [%p] as " 5363 "active from another process. actionid: %" PRIu64, 5364 aContext, aActionId)); 5365 return; 5366 } 5367 mActiveBrowsingContextInContentSetFromOtherProcess = true; 5368 mActiveBrowsingContextInContent = aContext; 5369 mActionIdForActiveBrowsingContextInContent = aActionId; 5370 MaybeUnlockPointer(aContext); 5371 } 5372 5373 void nsFocusManager::UnsetActiveBrowsingContextFromOtherProcess( 5374 BrowsingContext* aContext, uint64_t aActionId) { 5375 MOZ_ASSERT(!XRE_IsParentProcess()); 5376 MOZ_ASSERT(aContext); 5377 if (ActionIdComparableAndLower(aActionId, 5378 mActionIdForActiveBrowsingContextInContent)) { 5379 LOGFOCUS( 5380 ("Ignored an attempt to unset the active BrowsingContext [%p] from " 5381 "another process due to stale action id: %" PRIu64 ".", 5382 aContext, aActionId)); 5383 return; 5384 } 5385 if (mActiveBrowsingContextInContent == aContext) { 5386 mActiveBrowsingContextInContent = nullptr; 5387 mActionIdForActiveBrowsingContextInContent = aActionId; 5388 MaybeUnlockPointer(nullptr); 5389 } else { 5390 LOGFOCUS( 5391 ("Ignored an attempt to unset the active BrowsingContext [%p] from " 5392 "another process. actionid: %" PRIu64, 5393 aContext, aActionId)); 5394 } 5395 } 5396 5397 void nsFocusManager::ReviseActiveBrowsingContext( 5398 uint64_t aOldActionId, mozilla::dom::BrowsingContext* aContext, 5399 uint64_t aNewActionId) { 5400 MOZ_ASSERT(XRE_IsContentProcess()); 5401 if (mActionIdForActiveBrowsingContextInContent == aOldActionId) { 5402 LOGFOCUS(("Revising the active BrowsingContext [%p]. old actionid: %" PRIu64 5403 ", new " 5404 "actionid: %" PRIu64, 5405 aContext, aOldActionId, aNewActionId)); 5406 mActiveBrowsingContextInContent = aContext; 5407 mActionIdForActiveBrowsingContextInContent = aNewActionId; 5408 } else { 5409 LOGFOCUS( 5410 ("Ignored a stale attempt to revise the active BrowsingContext [%p]. " 5411 "old actionid: %" PRIu64 ", new actionid: %" PRIu64, 5412 aContext, aOldActionId, aNewActionId)); 5413 } 5414 } 5415 5416 void nsFocusManager::ReviseFocusedBrowsingContext( 5417 uint64_t aOldActionId, mozilla::dom::BrowsingContext* aContext, 5418 uint64_t aNewActionId) { 5419 MOZ_ASSERT(XRE_IsContentProcess()); 5420 if (mActionIdForFocusedBrowsingContextInContent == aOldActionId) { 5421 LOGFOCUS( 5422 ("Revising the focused BrowsingContext [%p]. old actionid: %" PRIu64 5423 ", new " 5424 "actionid: %" PRIu64, 5425 aContext, aOldActionId, aNewActionId)); 5426 mFocusedBrowsingContextInContent = aContext; 5427 mActionIdForFocusedBrowsingContextInContent = aNewActionId; 5428 mFocusedElement = nullptr; 5429 } else { 5430 LOGFOCUS( 5431 ("Ignored a stale attempt to revise the focused BrowsingContext [%p]. " 5432 "old actionid: %" PRIu64 ", new actionid: %" PRIu64, 5433 aContext, aOldActionId, aNewActionId)); 5434 } 5435 } 5436 5437 bool nsFocusManager::SetActiveBrowsingContextInChrome( 5438 mozilla::dom::BrowsingContext* aContext, uint64_t aActionId) { 5439 MOZ_ASSERT(aActionId); 5440 if (ProcessPendingActiveBrowsingContextActionId(aActionId, aContext)) { 5441 MOZ_DIAGNOSTIC_ASSERT(!ActionIdComparableAndLower( 5442 aActionId, mActionIdForActiveBrowsingContextInChrome)); 5443 mActiveBrowsingContextInChrome = aContext; 5444 mActionIdForActiveBrowsingContextInChrome = aActionId; 5445 return true; 5446 } 5447 return false; 5448 } 5449 5450 uint64_t nsFocusManager::GetActionIdForActiveBrowsingContextInChrome() const { 5451 return mActionIdForActiveBrowsingContextInChrome; 5452 } 5453 5454 uint64_t nsFocusManager::GetActionIdForFocusedBrowsingContextInChrome() const { 5455 return mActionIdForFocusedBrowsingContextInChrome; 5456 } 5457 5458 BrowsingContext* nsFocusManager::GetActiveBrowsingContextInChrome() { 5459 return mActiveBrowsingContextInChrome; 5460 } 5461 5462 void nsFocusManager::InsertNewFocusActionId(uint64_t aActionId) { 5463 LOGFOCUS(("InsertNewFocusActionId %" PRIu64, aActionId)); 5464 MOZ_ASSERT(XRE_IsParentProcess()); 5465 MOZ_ASSERT(!mPendingActiveBrowsingContextActions.Contains(aActionId)); 5466 mPendingActiveBrowsingContextActions.AppendElement(aActionId); 5467 MOZ_ASSERT(!mPendingFocusedBrowsingContextActions.Contains(aActionId)); 5468 mPendingFocusedBrowsingContextActions.AppendElement(aActionId); 5469 } 5470 5471 static void RemoveContentInitiatedActionsUntil( 5472 nsTArray<uint64_t>& aPendingActions, 5473 nsTArray<uint64_t>::index_type aUntil) { 5474 nsTArray<uint64_t>::index_type i = 0; 5475 while (i < aUntil) { 5476 auto [actionProc, actionId] = 5477 nsContentUtils::SplitProcessSpecificId(aPendingActions[i]); 5478 (void)actionId; 5479 if (actionProc) { 5480 aPendingActions.RemoveElementAt(i); 5481 --aUntil; 5482 continue; 5483 } 5484 ++i; 5485 } 5486 } 5487 5488 bool nsFocusManager::ProcessPendingActiveBrowsingContextActionId( 5489 uint64_t aActionId, bool aSettingToNonNull) { 5490 MOZ_ASSERT(XRE_IsParentProcess()); 5491 auto index = mPendingActiveBrowsingContextActions.IndexOf(aActionId); 5492 if (index == nsTArray<uint64_t>::NoIndex) { 5493 return false; 5494 } 5495 // When aSettingToNonNull is true, we need to remove one more 5496 // element to remove the action id itself in addition to 5497 // removing the older ones. 5498 if (aSettingToNonNull) { 5499 index++; 5500 } 5501 auto [actionProc, actionId] = 5502 nsContentUtils::SplitProcessSpecificId(aActionId); 5503 (void)actionId; 5504 if (actionProc) { 5505 // Action from content: We allow parent-initiated actions 5506 // to take precedence over content-initiated ones, so we 5507 // remove only prior content-initiated actions. 5508 RemoveContentInitiatedActionsUntil(mPendingActiveBrowsingContextActions, 5509 index); 5510 } else { 5511 // Action from chrome 5512 mPendingActiveBrowsingContextActions.RemoveElementsAt(0, index); 5513 } 5514 return true; 5515 } 5516 5517 bool nsFocusManager::ProcessPendingFocusedBrowsingContextActionId( 5518 uint64_t aActionId) { 5519 MOZ_ASSERT(XRE_IsParentProcess()); 5520 auto index = mPendingFocusedBrowsingContextActions.IndexOf(aActionId); 5521 if (index == nsTArray<uint64_t>::NoIndex) { 5522 return false; 5523 } 5524 5525 auto [actionProc, actionId] = 5526 nsContentUtils::SplitProcessSpecificId(aActionId); 5527 (void)actionId; 5528 if (actionProc) { 5529 // Action from content: We allow parent-initiated actions 5530 // to take precedence over content-initiated ones, so we 5531 // remove only prior content-initiated actions. 5532 RemoveContentInitiatedActionsUntil(mPendingFocusedBrowsingContextActions, 5533 index); 5534 } else { 5535 // Action from chrome 5536 mPendingFocusedBrowsingContextActions.RemoveElementsAt(0, index); 5537 } 5538 return true; 5539 } 5540 5541 // static 5542 uint64_t nsFocusManager::GenerateFocusActionId() { 5543 uint64_t id = 5544 nsContentUtils::GenerateProcessSpecificId(++sFocusActionCounter); 5545 if (XRE_IsParentProcess()) { 5546 nsFocusManager* fm = GetFocusManager(); 5547 if (fm) { 5548 fm->InsertNewFocusActionId(id); 5549 } 5550 } else { 5551 mozilla::dom::ContentChild* contentChild = 5552 mozilla::dom::ContentChild::GetSingleton(); 5553 MOZ_ASSERT(contentChild); 5554 contentChild->SendInsertNewFocusActionId(id); 5555 } 5556 LOGFOCUS(("GenerateFocusActionId %" PRIu64, id)); 5557 return id; 5558 } 5559 5560 static bool IsInPointerLockContext(nsPIDOMWindowOuter* aWin) { 5561 return PointerLockManager::IsInLockContext(aWin ? aWin->GetBrowsingContext() 5562 : nullptr); 5563 } 5564 5565 void nsFocusManager::SetFocusedWindowInternal(nsPIDOMWindowOuter* aWindow, 5566 uint64_t aActionId, 5567 bool aSyncBrowsingContext) { 5568 if (XRE_IsParentProcess() && !PointerUnlocker::sActiveUnlocker && 5569 IsInPointerLockContext(mFocusedWindow) && 5570 !IsInPointerLockContext(aWindow)) { 5571 nsCOMPtr<nsIRunnable> runnable = new PointerUnlocker(); 5572 NS_DispatchToCurrentThread(runnable); 5573 } 5574 5575 // Update the last focus time on any affected documents 5576 if (aWindow && aWindow != mFocusedWindow) { 5577 const TimeStamp now(TimeStamp::Now()); 5578 for (Document* doc = aWindow->GetExtantDoc(); doc; 5579 doc = doc->GetInProcessParentDocument()) { 5580 doc->SetLastFocusTime(now); 5581 } 5582 } 5583 5584 // This function may be called with zero action id to indicate that the 5585 // action id should be ignored. 5586 if (XRE_IsContentProcess() && aActionId && 5587 ActionIdComparableAndLower(aActionId, 5588 mActionIdForFocusedBrowsingContextInContent)) { 5589 // Unclear if this ever happens. 5590 LOGFOCUS( 5591 ("Ignored an attempt to set an in-process BrowsingContext as " 5592 "focused due to stale action id %" PRIu64 ".", 5593 aActionId)); 5594 return; 5595 } 5596 5597 mFocusedWindow = aWindow; 5598 BrowsingContext* bc = aWindow ? aWindow->GetBrowsingContext() : nullptr; 5599 if (aSyncBrowsingContext) { 5600 MOZ_ASSERT(aActionId, 5601 "aActionId must not be zero if aSyncBrowsingContext is true"); 5602 SetFocusedBrowsingContext(bc, aActionId); 5603 } else if (XRE_IsContentProcess()) { 5604 MOZ_ASSERT(mFocusedBrowsingContextInContent == bc, 5605 "Not syncing BrowsingContext even when different."); 5606 } 5607 } 5608 5609 void nsFocusManager::NotifyOfReFocus(Element& aElement) { 5610 nsPIDOMWindowOuter* window = GetCurrentWindow(&aElement); 5611 if (!window || window != mFocusedWindow) { 5612 return; 5613 } 5614 if (!aElement.IsInComposedDoc() || IsNonFocusableRoot(&aElement)) { 5615 return; 5616 } 5617 nsIDocShell* docShell = window->GetDocShell(); 5618 if (!docShell) { 5619 return; 5620 } 5621 RefPtr<PresShell> presShell = docShell->GetPresShell(); 5622 if (!presShell) { 5623 return; 5624 } 5625 RefPtr<nsPresContext> presContext = presShell->GetPresContext(); 5626 if (!presContext) { 5627 return; 5628 } 5629 IMEStateManager::OnReFocus(*presContext, aElement); 5630 } 5631 5632 void nsFocusManager::MarkUncollectableForCCGeneration(uint32_t aGeneration) { 5633 if (!sInstance) { 5634 return; 5635 } 5636 5637 if (sInstance->mActiveWindow) { 5638 sInstance->mActiveWindow->MarkUncollectableForCCGeneration(aGeneration); 5639 } 5640 if (sInstance->mFocusedWindow) { 5641 sInstance->mFocusedWindow->MarkUncollectableForCCGeneration(aGeneration); 5642 } 5643 if (sInstance->mWindowBeingLowered) { 5644 sInstance->mWindowBeingLowered->MarkUncollectableForCCGeneration( 5645 aGeneration); 5646 } 5647 if (sInstance->mFocusedElement) { 5648 sInstance->mFocusedElement->OwnerDoc()->MarkUncollectableForCCGeneration( 5649 aGeneration); 5650 } 5651 } 5652 5653 bool nsFocusManager::CanSkipFocus(nsIContent* aContent) { 5654 if (!aContent) { 5655 return false; 5656 } 5657 5658 if (mFocusedElement == aContent) { 5659 return true; 5660 } 5661 5662 nsIDocShell* ds = aContent->OwnerDoc()->GetDocShell(); 5663 if (!ds) { 5664 return true; 5665 } 5666 5667 if (XRE_IsParentProcess()) { 5668 nsCOMPtr<nsIDocShellTreeItem> root; 5669 ds->GetInProcessRootTreeItem(getter_AddRefs(root)); 5670 nsCOMPtr<nsPIDOMWindowOuter> newRootWindow = 5671 root ? root->GetWindow() : nullptr; 5672 if (mActiveWindow != newRootWindow) { 5673 nsPIDOMWindowOuter* outerWindow = aContent->OwnerDoc()->GetWindow(); 5674 if (outerWindow && outerWindow->GetFocusedElement() == aContent) { 5675 return true; 5676 } 5677 } 5678 } else { 5679 BrowsingContext* bc = aContent->OwnerDoc()->GetBrowsingContext(); 5680 BrowsingContext* top = bc ? bc->Top() : nullptr; 5681 if (GetActiveBrowsingContext() != top) { 5682 nsPIDOMWindowOuter* outerWindow = aContent->OwnerDoc()->GetWindow(); 5683 if (outerWindow && outerWindow->GetFocusedElement() == aContent) { 5684 return true; 5685 } 5686 } 5687 } 5688 5689 return false; 5690 } 5691 5692 static IsFocusableFlags FocusManagerFlagsToIsFocusableFlags(uint32_t aFlags) { 5693 auto flags = IsFocusableFlags(0); 5694 if (aFlags & nsIFocusManager::FLAG_BYMOUSE) { 5695 flags |= IsFocusableFlags::WithMouse; 5696 } 5697 return flags; 5698 } 5699 5700 /* static */ 5701 Element* nsFocusManager::GetTheFocusableArea(Element* aTarget, 5702 uint32_t aFlags) { 5703 MOZ_ASSERT(aTarget); 5704 nsIFrame* frame = aTarget->GetPrimaryFrame(); 5705 if (!frame) { 5706 return nullptr; 5707 } 5708 5709 // If focus target is the document element of its Document. 5710 if (aTarget == aTarget->OwnerDoc()->GetRootElement()) { 5711 // the root content can always be focused, 5712 // except in userfocusignored context. 5713 return aTarget; 5714 } 5715 5716 // If focus target is an area element with one or more shapes that are 5717 // focusable areas. 5718 if (auto* area = HTMLAreaElement::FromNode(aTarget)) { 5719 return IsAreaElementFocusable(*area) ? area : nullptr; 5720 } 5721 5722 // For these 3 steps mentioned in the spec 5723 // 1. If focus target is an element with one or more scrollable regions that 5724 // are focusable areas 5725 // 2. If focus target is a navigable 5726 // 3. If focus target is a navigable container with a non-null content 5727 // navigable 5728 // nsIFrame::IsFocusable will effectively perform the checks for them. 5729 IsFocusableFlags flags = FocusManagerFlagsToIsFocusableFlags(aFlags); 5730 if (frame->IsFocusable(flags)) { 5731 return aTarget; 5732 } 5733 5734 // If focus target is a shadow host whose shadow root's delegates focus is 5735 // true 5736 if (ShadowRoot* root = aTarget->GetShadowRoot()) { 5737 if (root->DelegatesFocus()) { 5738 // If focus target is a shadow-including inclusive ancestor of the 5739 // currently focused area of a top-level browsing context's DOM anchor, 5740 // then return the already-focused element. 5741 if (nsPIDOMWindowInner* innerWindow = 5742 aTarget->OwnerDoc()->GetInnerWindow()) { 5743 if (Element* focusedElement = innerWindow->GetFocusedElement()) { 5744 if (focusedElement->IsShadowIncludingInclusiveDescendantOf(aTarget)) { 5745 return focusedElement; 5746 } 5747 } 5748 } 5749 5750 if (Element* firstFocusable = root->GetFocusDelegate(flags)) { 5751 return firstFocusable; 5752 } 5753 } 5754 } 5755 return nullptr; 5756 } 5757 5758 /* static */ 5759 bool nsFocusManager::IsAreaElementFocusable(HTMLAreaElement& aArea) { 5760 nsIFrame* frame = aArea.GetPrimaryFrame(); 5761 if (!frame) { 5762 return false; 5763 } 5764 // HTML areas do not have their own frame, and the img frame we get from 5765 // GetPrimaryFrame() is not relevant as to whether it is focusable or 5766 // not, so we have to do all the relevant checks manually for them. 5767 return frame->IsVisibleConsideringAncestors() && 5768 aArea.IsFocusableWithoutStyle(); 5769 } 5770 5771 nsresult NS_NewFocusManager(nsIFocusManager** aResult) { 5772 NS_IF_ADDREF(*aResult = nsFocusManager::GetFocusManager()); 5773 return NS_OK; 5774 }