tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }