Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/dom/base/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 211 kB image not shown  

Quelle  nsFocusManager.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "mozilla/dom/BrowserParent.h"

#include "nsFocusManager.h"

#include "LayoutConstants.h"
#include "ChildIterator.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsGkAtoms.h"
#include "nsContentUtils.h"
#include "ContentParent.h"
#include "nsPIDOMWindow.h"
#include "nsIContentInlines.h"
#include "nsIDocShell.h"
#include "nsIDocShellTreeOwner.h"
#include "nsIFormControl.h"
#include "nsLayoutUtils.h"
#include "nsFrameTraversal.h"
#include "nsIWebNavigation.h"
#include "nsCaret.h"
#include "nsIBaseWindow.h"
#include "nsIAppWindow.h"
#include "nsTextControlFrame.h"
#include "nsThreadUtils.h"
#include "nsViewManager.h"
#include "nsFrameSelection.h"
#include "mozilla/dom/Selection.h"
#include "nsXULPopupManager.h"
#include "nsMenuPopupFrame.h"
#include "nsIScriptError.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsIPrincipal.h"
#include "nsIObserverService.h"
#include "BrowserChild.h"
#include "nsFrameLoader.h"
#include "nsHTMLDocument.h"
#include "nsNetUtil.h"
#include "nsRange.h"
#include "nsFrameLoaderOwner.h"
#include "nsQueryObject.h"
#include "nsIXULRuntime.h"

#include "mozilla/AccessibleCaretEventHub.h"
#include "mozilla/ContentEvents.h"
#include "mozilla/FocusModel.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ElementBinding.h"
#include "mozilla/dom/HTMLImageElement.h"
#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/HTMLSlotElement.h"
#include "mozilla/dom/HTMLAreaElement.h"
#include "mozilla/dom/BrowserBridgeChild.h"
#include "mozilla/dom/Text.h"
#include "mozilla/dom/XULPopupElement.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/WindowGlobalChild.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/HTMLEditor.h"
#include "mozilla/IMEStateManager.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/Maybe.h"
#include "mozilla/PointerLockManager.h"
#include "mozilla/Preferences.h"
#include "mozilla/PresShell.h"
#include "mozilla/Services.h"
#include "mozilla/Unused.h"
#include "mozilla/StaticPrefs_accessibility.h"
#include "mozilla/StaticPrefs_full_screen_api.h"
#include "mozilla/Try.h"
#include "mozilla/widget/IMEData.h"
#include <algorithm>

#include "nsIDOMXULMenuListElement.h"

#ifdef ACCESSIBILITY
#  include "nsAccessibilityService.h"
#endif

using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::widget;

// Two types of focus pr logging are available:
//   'Focus' for normal focus manager calls
//   'FocusNavigation' for tab and document navigation
LazyLogModule gFocusLog("Focus");
LazyLogModule gFocusNavigationLog("FocusNavigation");

#define LOGFOCUS(args) MOZ_LOG(gFocusLog, mozilla::LogLevel::Debug, args)
#define LOGFOCUSNAVIGATION(args) \
  MOZ_LOG(gFocusNavigationLog, mozilla::LogLevel::Debug, args)

#define LOGTAG(log, format, content)                      \
  if (MOZ_LOG_TEST(log, LogLevel::Debug)) {               \
    nsAutoCString tag("(none)"_ns);                       \
    if (content) {                                        \
      content->NodeInfo()->NameAtom()->ToUTF8String(tag); \
    }                                                     \
    MOZ_LOG(log, LogLevel::Debug, (format, tag.get()));   \
  }

#define LOGCONTENT(format, content) LOGTAG(gFocusLog, format, content)
#define LOGCONTENTNAVIGATION(format, content) \
  LOGTAG(gFocusNavigationLog, format, content)

struct nsDelayedBlurOrFocusEvent {
  nsDelayedBlurOrFocusEvent(EventMessage aEventMessage, PresShell* aPresShell,
                            Document* aDocument, EventTarget* aTarget,
                            EventTarget* aRelatedTarget)
      : mPresShell(aPresShell),
        mDocument(aDocument),
        mTarget(aTarget),
        mEventMessage(aEventMessage),
        mRelatedTarget(aRelatedTarget) {}

  nsDelayedBlurOrFocusEvent(const nsDelayedBlurOrFocusEvent& aOther)
      : mPresShell(aOther.mPresShell),
        mDocument(aOther.mDocument),
        mTarget(aOther.mTarget),
        mEventMessage(aOther.mEventMessage) {}

  RefPtr<PresShell> mPresShell;
  nsCOMPtr<Document> mDocument;
  nsCOMPtr<EventTarget> mTarget;
  EventMessage mEventMessage;
  nsCOMPtr<EventTarget> mRelatedTarget;
};

inline void ImplCycleCollectionUnlink(nsDelayedBlurOrFocusEvent& aField) {
  aField.mPresShell = nullptr;
  aField.mDocument = nullptr;
  aField.mTarget = nullptr;
  aField.mRelatedTarget = nullptr;
}

inline void ImplCycleCollectionTraverse(
    nsCycleCollectionTraversalCallback& aCallback,
    nsDelayedBlurOrFocusEvent& aField, const char* aName, uint32_t aFlags = 0) {
  CycleCollectionNoteChild(
      aCallback, static_cast<nsIDocumentObserver*>(aField.mPresShell.get()),
      aName, aFlags);
  CycleCollectionNoteChild(aCallback, aField.mDocument.get(), aName, aFlags);
  CycleCollectionNoteChild(aCallback, aField.mTarget.get(), aName, aFlags);
  CycleCollectionNoteChild(aCallback, aField.mRelatedTarget.get(), aName,
                           aFlags);
}

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFocusManager)
  NS_INTERFACE_MAP_ENTRY(nsIFocusManager)
  NS_INTERFACE_MAP_ENTRY(nsIObserver)
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFocusManager)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFocusManager)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFocusManager)

NS_IMPL_CYCLE_COLLECTION_WEAK(nsFocusManager, mActiveWindow,
                              mActiveBrowsingContextInContent,
                              mActiveBrowsingContextInChrome, mFocusedWindow,
                              mFocusedBrowsingContextInContent,
                              mFocusedBrowsingContextInChrome, mFocusedElement,
                              mFirstBlurEvent, mFirstFocusEvent,
                              mWindowBeingLowered, mDelayedBlurFocusEvents)

StaticRefPtr<nsFocusManager> nsFocusManager::sInstance;
bool nsFocusManager::sTestMode = false;
uint64_t nsFocusManager::sFocusActionCounter = 0;

static const char* kObservedPrefs[] = {"accessibility.browsewithcaret",
                                       "focusmanager.testmode", nullptr};

nsFocusManager::nsFocusManager()
    : mActionIdForActiveBrowsingContextInContent(0),
      mActionIdForActiveBrowsingContextInChrome(0),
      mActionIdForFocusedBrowsingContextInContent(0),
      mActionIdForFocusedBrowsingContextInChrome(0),
      mActiveBrowsingContextInContentSetFromOtherProcess(false),
      mEventHandlingNeedsFlush(false) {}

nsFocusManager::~nsFocusManager() {
  Preferences::UnregisterCallbacks(nsFocusManager::PrefChanged, kObservedPrefs,
                                   this);

  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
  if (obs) {
    obs->RemoveObserver(this"xpcom-shutdown");
  }
}

// static
nsresult nsFocusManager::Init() {
  sInstance = new nsFocusManager();

  sTestMode = Preferences::GetBool("focusmanager.testmode"false);

  Preferences::RegisterCallbacks(nsFocusManager::PrefChanged, kObservedPrefs,
                                 sInstance.get());

  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
  if (obs) {
    obs->AddObserver(sInstance, "xpcom-shutdown"true);
  }

  return NS_OK;
}

// static
void nsFocusManager::Shutdown() { sInstance = nullptr; }

// static
void nsFocusManager::PrefChanged(const char* aPref, void* aSelf) {
  if (RefPtr<nsFocusManager> fm = static_cast<nsFocusManager*>(aSelf)) {
    fm->PrefChanged(aPref);
  }
}

void nsFocusManager::PrefChanged(const char* aPref) {
  nsDependentCString pref(aPref);
  if (pref.EqualsLiteral("accessibility.browsewithcaret")) {
    UpdateCaretForCaretBrowsingMode();
  } else if (pref.EqualsLiteral("focusmanager.testmode")) {
    sTestMode = Preferences::GetBool("focusmanager.testmode"false);
  }
}

NS_IMETHODIMP
nsFocusManager::Observe(nsISupports* aSubject, const char* aTopic,
                        const char16_t* aData) {
  if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
    mActiveWindow = nullptr;
    mActiveBrowsingContextInContent = nullptr;
    mActionIdForActiveBrowsingContextInContent = 0;
    mActionIdForFocusedBrowsingContextInContent = 0;
    mActiveBrowsingContextInChrome = nullptr;
    mActionIdForActiveBrowsingContextInChrome = 0;
    mActionIdForFocusedBrowsingContextInChrome = 0;
    mFocusedWindow = nullptr;
    mFocusedBrowsingContextInContent = nullptr;
    mFocusedBrowsingContextInChrome = nullptr;
    mFocusedElement = nullptr;
    mFirstBlurEvent = nullptr;
    mFirstFocusEvent = nullptr;
    mWindowBeingLowered = nullptr;
    mDelayedBlurFocusEvents.Clear();
  }

  return NS_OK;
}

static bool ActionIdComparableAndLower(uint64_t aActionId,
                                       uint64_t aReference) {
  MOZ_ASSERT(aActionId, "Uninitialized action id");
  auto [actionProc, actionId] =
      nsContentUtils::SplitProcessSpecificId(aActionId);
  auto [refProc, refId] = nsContentUtils::SplitProcessSpecificId(aReference);
  return actionProc == refProc && actionId < refId;
}

// given a frame content node, retrieve the nsIDOMWindow displayed in it
static nsPIDOMWindowOuter* GetContentWindow(nsIContent* aContent) {
  if (Document* doc = aContent->GetComposedDoc()) {
    if (Document* subdoc = doc->GetSubDocumentFor(aContent)) {
      return subdoc->GetWindow();
    }
  }
  return nullptr;
}

bool nsFocusManager::IsFocused(nsIContent* aContent) {
  if (!aContent || !mFocusedElement) {
    return false;
  }
  return aContent == mFocusedElement;
}

bool nsFocusManager::IsTestMode() { return sTestMode; }

bool nsFocusManager::IsInActiveWindow(BrowsingContext* aBC) const {
  RefPtr<BrowsingContext> top = aBC->Top();
  if (XRE_IsParentProcess()) {
    top = top->Canonical()->TopCrossChromeBoundary();
  }
  return IsSameOrAncestor(top, GetActiveBrowsingContext());
}

// get the current window for the given content node
static nsPIDOMWindowOuter* GetCurrentWindow(nsIContent* aContent) {
  Document* doc = aContent->GetComposedDoc();
  return doc ? doc->GetWindow() : nullptr;
}

// static
Element* nsFocusManager::GetFocusedDescendant(
    nsPIDOMWindowOuter* aWindow, SearchRange aSearchRange,
    nsPIDOMWindowOuter** aFocusedWindow) {
  NS_ENSURE_TRUE(aWindow, nullptr);

  *aFocusedWindow = nullptr;

  Element* currentElement = nullptr;
  nsPIDOMWindowOuter* window = aWindow;
  for (;;) {
    *aFocusedWindow = window;
    currentElement = window->GetFocusedElement();
    if (!currentElement || aSearchRange == eOnlyCurrentWindow) {
      break;
    }

    window = GetContentWindow(currentElement);
    if (!window) {
      break;
    }

    if (aSearchRange == eIncludeAllDescendants) {
      continue;
    }

    MOZ_ASSERT(aSearchRange == eIncludeVisibleDescendants);

    // If the child window doesn't have PresShell, it means the window is
    // invisible.
    nsIDocShell* docShell = window->GetDocShell();
    if (!docShell) {
      break;
    }
    if (!docShell->GetPresShell()) {
      break;
    }
  }

  NS_IF_ADDREF(*aFocusedWindow);

  return currentElement;
}

// static
InputContextAction::Cause nsFocusManager::GetFocusMoveActionCause(
    uint32_t aFlags) {
  if (aFlags & nsIFocusManager::FLAG_BYTOUCH) {
    return InputContextAction::CAUSE_TOUCH;
  } else if (aFlags & nsIFocusManager::FLAG_BYMOUSE) {
    return InputContextAction::CAUSE_MOUSE;
  } else if (aFlags & nsIFocusManager::FLAG_BYKEY) {
    return InputContextAction::CAUSE_KEY;
  } else if (aFlags & nsIFocusManager::FLAG_BYLONGPRESS) {
    return InputContextAction::CAUSE_LONGPRESS;
  }
  return InputContextAction::CAUSE_UNKNOWN;
}

NS_IMETHODIMP
nsFocusManager::GetActiveWindow(mozIDOMWindowProxy** aWindow) {
  MOZ_ASSERT(XRE_IsParentProcess(),
             "Must not be called outside the parent process.");
  NS_IF_ADDREF(*aWindow = mActiveWindow);
  return NS_OK;
}

NS_IMETHODIMP
nsFocusManager::GetActiveBrowsingContext(BrowsingContext** aBrowsingContext) {
  NS_IF_ADDREF(*aBrowsingContext = GetActiveBrowsingContext());
  return NS_OK;
}

void nsFocusManager::FocusWindow(nsPIDOMWindowOuter* aWindow,
                                 CallerType aCallerType) {
  if (RefPtr<nsFocusManager> fm = sInstance) {
    fm->SetFocusedWindowWithCallerType(aWindow, aCallerType);
  }
}

NS_IMETHODIMP
nsFocusManager::GetFocusedWindow(mozIDOMWindowProxy** aFocusedWindow) {
  NS_IF_ADDREF(*aFocusedWindow = mFocusedWindow);
  return NS_OK;
}

NS_IMETHODIMP
nsFocusManager::GetFocusedContentBrowsingContext(
    BrowsingContext** aBrowsingContext) {
  MOZ_DIAGNOSTIC_ASSERT(
      XRE_IsParentProcess(),
      "We only have use cases for this in the parent process");
  NS_IF_ADDREF(*aBrowsingContext = GetFocusedBrowsingContextInChrome());
  return NS_OK;
}

NS_IMETHODIMP
nsFocusManager::GetActiveContentBrowsingContext(
    BrowsingContext** aBrowsingContext) {
  MOZ_DIAGNOSTIC_ASSERT(
      XRE_IsParentProcess(),
      "We only have use cases for this in the parent process");
  NS_IF_ADDREF(*aBrowsingContext = GetActiveBrowsingContextInChrome());
  return NS_OK;
}

nsresult nsFocusManager::SetFocusedWindowWithCallerType(
    mozIDOMWindowProxy* aWindowToFocus, CallerType aCallerType) {
  LOGFOCUS(("<>"));

  nsCOMPtr<nsPIDOMWindowOuter> windowToFocus =
      nsPIDOMWindowOuter::From(aWindowToFocus);
  NS_ENSURE_TRUE(windowToFocus, NS_ERROR_FAILURE);

  nsCOMPtr<Element> frameElement = windowToFocus->GetFrameElementInternal();
  Maybe<uint64_t> existingActionId;
  if (frameElement) {
    // pass false for aFocusChanged so that the caret does not get updated
    // and scrolling does not occur.
    existingActionId = SetFocusInner(frameElement, 0, falsetrue);
  } else if (auto* bc = windowToFocus->GetBrowsingContext();
             bc && !bc->IsTop()) {
    // No frameElement means windowToFocus is an OOP iframe, so
    // the above SetFocusInner is not called. That means the focus
    // of the currently focused BC is not going to be cleared. So
    // we do that manually here.
    if (RefPtr<BrowsingContext> focusedBC = GetFocusedBrowsingContext()) {
      // If focusedBC is an ancestor of bc, blur will be handled
      // correctly by nsFocusManager::AdjustWindowFocus.
      if (!IsSameOrAncestor(focusedBC, bc)) {
        existingActionId.emplace(sInstance->GenerateFocusActionId());
        Blur(focusedBC, nullptr, truetruefalse, existingActionId.value());
      }
    }
  } else {
    // this is a top-level window. If the window has a child frame focused,
    // clear the focus. Otherwise, focus should already be in this frame, or
    // already cleared. This ensures that focus will be in this frame and not
    // in a child.
    if (Element* el = windowToFocus->GetFocusedElement()) {
      if (nsCOMPtr<nsPIDOMWindowOuter> childWindow = GetContentWindow(el)) {
        ClearFocus(windowToFocus);
      }
    }
  }

  nsCOMPtr<nsPIDOMWindowOuter> rootWindow = windowToFocus->GetPrivateRoot();
  const uint64_t actionId = existingActionId.isSome()
                                ? existingActionId.value()
                                : sInstance->GenerateFocusActionId();
  if (rootWindow) {
    RaiseWindow(rootWindow, aCallerType, actionId);
  }

  LOGFOCUS(("< PRIu64 ">>", actionId));

  return NS_OK;
}

NS_IMETHODIMP nsFocusManager::SetFocusedWindow(
    mozIDOMWindowProxy* aWindowToFocus) {
  return SetFocusedWindowWithCallerType(aWindowToFocus, CallerType::System);
}

NS_IMETHODIMP
nsFocusManager::GetFocusedElement(Element** aFocusedElement) {
  RefPtr<Element> focusedElement = mFocusedElement;
  focusedElement.forget(aFocusedElement);
  return NS_OK;
}

uint32_t nsFocusManager::GetLastFocusMethod(nsPIDOMWindowOuter* aWindow) const {
  nsPIDOMWindowOuter* window = aWindow ? aWindow : mFocusedWindow.get();
  uint32_t method = window ? window->GetFocusMethod() : 0;
  NS_ASSERTION((method & METHOD_MASK) == method, "invalid focus method");
  return method;
}

NS_IMETHODIMP
nsFocusManager::GetLastFocusMethod(mozIDOMWindowProxy* aWindow,
                                   uint32_t* aLastFocusMethod) {
  *aLastFocusMethod = GetLastFocusMethod(nsPIDOMWindowOuter::From(aWindow));
  return NS_OK;
}

NS_IMETHODIMP
nsFocusManager::SetFocus(Element* aElement, uint32_t aFlags) {
  LOGFOCUS(("<>"));

  NS_ENSURE_ARG(aElement);

  SetFocusInner(aElement, aFlags, truetrue);

  LOGFOCUS(("<>"));

  return NS_OK;
}

NS_IMETHODIMP
nsFocusManager::ElementIsFocusable(Element* aElement, uint32_t aFlags,
                                   bool* aIsFocusable) {
  NS_ENSURE_TRUE(aElement, NS_ERROR_INVALID_ARG);
  *aIsFocusable = !!FlushAndCheckIfFocusable(aElement, aFlags);
  return NS_OK;
}

MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
nsFocusManager::MoveFocus(mozIDOMWindowProxy* aWindow, Element* aStartElement,
                          uint32_t aType, uint32_t aFlags, Element** aElement) {
  *aElement = nullptr;

  LOGFOCUS(("<>", aType, aFlags));

  if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug) && mFocusedWindow) {
    Document* doc = mFocusedWindow->GetExtantDoc();
    if (doc && doc->GetDocumentURI()) {
      LOGFOCUS((" Focused Window: %p %s", mFocusedWindow.get(),
                doc->GetDocumentURI()->GetSpecOrDefault().get()));
    }
  }

  LOGCONTENT(" Current Focus: %s", mFocusedElement.get());

  // use FLAG_BYMOVEFOCUS when switching focus with MoveFocus unless one of
  // the other focus methods is already set, or we're just moving to the root
  // or caret position.
  if (aType != MOVEFOCUS_ROOT && aType != MOVEFOCUS_CARET &&
      (aFlags & METHOD_MASK) == 0) {
    aFlags |= FLAG_BYMOVEFOCUS;
  }

  nsCOMPtr<nsPIDOMWindowOuter> window;
  if (aStartElement) {
    window = GetCurrentWindow(aStartElement);
  } else {
    window = aWindow ? nsPIDOMWindowOuter::From(aWindow) : mFocusedWindow.get();
  }

  NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);

  // Flush to ensure that focusability of descendants is computed correctly.
  if (RefPtr<Document> doc = window->GetExtantDoc()) {
    doc->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames);
  }

  bool noParentTraversal = aFlags & FLAG_NOPARENTFRAME;
  nsCOMPtr<nsIContent> newFocus;
  nsresult rv = DetermineElementToMoveFocus(window, aStartElement, aType,
                                            noParentTraversal, true,
                                            getter_AddRefs(newFocus));
  if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
    return NS_OK;
  }

  NS_ENSURE_SUCCESS(rv, rv);

  LOGCONTENTNAVIGATION("Element to be focused: %s", newFocus.get());

  if (newFocus && newFocus->IsElement()) {
    // for caret movement, pass false for the aFocusChanged argument,
    // otherwise the caret will end up moving to the focus position. This
    // would be a problem because the caret would move to the beginning of the
    // focused link making it impossible to navigate the caret over a link.
    SetFocusInner(MOZ_KnownLive(newFocus->AsElement()), aFlags,
                  aType != MOVEFOCUS_CARET, true);
    *aElement = do_AddRef(newFocus->AsElement()).take();
  } else if (aType == MOVEFOCUS_ROOT || aType == MOVEFOCUS_CARET) {
    // no content was found, so clear the focus for these two types.
    ClearFocus(window);
  }

  LOGFOCUS(("<>"));

  return NS_OK;
}

NS_IMETHODIMP
nsFocusManager::ClearFocus(mozIDOMWindowProxy* aWindow) {
  LOGFOCUS(("<>"));

  // if the window to clear is the focused window or an ancestor of the
  // focused window, then blur the existing focused content. Otherwise, the
  // focus is somewhere else so just update the current node.
  NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
  nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);

  if (IsSameOrAncestor(window, GetFocusedBrowsingContext())) {
    RefPtr<BrowsingContext> bc = window->GetBrowsingContext();
    RefPtr<BrowsingContext> focusedBC = GetFocusedBrowsingContext();
    const bool isAncestor = (focusedBC != bc);
    RefPtr<BrowsingContext> ancestorBC = isAncestor ? bc : nullptr;
    if (Blur(focusedBC, ancestorBC, isAncestor, truefalse,
             GenerateFocusActionId())) {
      // if we are clearing the focus on an ancestor of the focused window,
      // the ancestor will become the new focused window, so focus it
      if (isAncestor) {
        // Intentionally use a new actionId here because the above
        // Blur() will clear the focus of the ancestors of focusedBC, and
        // this Focus() call might need to update the focus of those ancestors,
        // so it needs to have a newer actionId to make that happen.
        Focus(window, nullptr, 0, truefalsefalsetrue,
              GenerateFocusActionId());
      }
    }
  } else {
    window->SetFocusedElement(nullptr);
  }

  LOGFOCUS(("<>"));

  return NS_OK;
}

NS_IMETHODIMP
nsFocusManager::GetFocusedElementForWindow(mozIDOMWindowProxy* aWindow,
                                           bool aDeep,
                                           mozIDOMWindowProxy** aFocusedWindow,
                                           Element** aElement) {
  *aElement = nullptr;
  if (aFocusedWindow) {
    *aFocusedWindow = nullptr;
  }

  NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
  nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);

  nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
  RefPtr<Element> focusedElement =
      GetFocusedDescendant(window,
                           aDeep ? nsFocusManager::eIncludeAllDescendants
                                 : nsFocusManager::eOnlyCurrentWindow,
                           getter_AddRefs(focusedWindow));

  focusedElement.forget(aElement);

  if (aFocusedWindow) {
    NS_IF_ADDREF(*aFocusedWindow = focusedWindow);
  }

  return NS_OK;
}

NS_IMETHODIMP
nsFocusManager::MoveCaretToFocus(mozIDOMWindowProxy* aWindow) {
  nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(aWindow);
  nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav);
  if (dsti) {
    if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) {
      nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(dsti);
      NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);

      // don't move the caret for editable documents
      bool isEditable;
      docShell->GetEditable(&isEditable);
      if (isEditable) {
        return NS_OK;
      }

      RefPtr<PresShell> presShell = docShell->GetPresShell();
      NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);

      nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
      if (RefPtr<Element> focusedElement = window->GetFocusedElement()) {
        MoveCaretToFocus(presShell, focusedElement);
      }
    }
  }

  return NS_OK;
}

void nsFocusManager::WindowRaised(mozIDOMWindowProxy* aWindow,
                                  uint64_t aActionId) {
  if (!aWindow) {
    return;
  }

  nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
  BrowsingContext* bc = window->GetBrowsingContext();

  if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
    LOGFOCUS(("Window %p Raised [Currently: %p %p] actionid: %" PRIu64, aWindow,
              mActiveWindow.get(), mFocusedWindow.get(), aActionId));
    Document* doc = window->GetExtantDoc();
    if (doc && doc->GetDocumentURI()) {
      LOGFOCUS((" Raised Window: %p %s", aWindow,
                doc->GetDocumentURI()->GetSpecOrDefault().get()));
    }
    if (mActiveWindow) {
      doc = mActiveWindow->GetExtantDoc();
      if (doc && doc->GetDocumentURI()) {
        LOGFOCUS((" Active Window: %p %s", mActiveWindow.get(),
                  doc->GetDocumentURI()->GetSpecOrDefault().get()));
      }
    }
  }

  if (XRE_IsParentProcess()) {
    if (mActiveWindow == window) {
      // The window is already active, so there is no need to focus anything,
      // but make sure that the right widget is focused. This is a special case
      // for Windows because when restoring a minimized window, a second
      // activation will occur and the top-level widget could be focused instead
      // of the child we want. We solve this by calling SetFocus to ensure that
      // what the focus manager thinks should be the current widget is actually
      // focused.
      EnsureCurrentWidgetFocused(CallerType::System);
      return;
    }

    // lower the existing window, if any. This shouldn't happen usually.
    if (nsCOMPtr<nsPIDOMWindowOuter> activeWindow = mActiveWindow) {
      WindowLowered(activeWindow, aActionId);
    }
  } else if (bc->IsTop()) {
    BrowsingContext* active = GetActiveBrowsingContext();
    if (active == bc && !mActiveBrowsingContextInContentSetFromOtherProcess) {
      // EnsureCurrentWidgetFocused() should not be necessary with
      // PuppetWidget.
      return;
    }

    if (active && active != bc) {
      if (active->IsInProcess()) {
        nsCOMPtr<nsPIDOMWindowOuter> activeWindow = active->GetDOMWindow();
        WindowLowered(activeWindow, aActionId);
      }
      // No else, because trying to lower other-process windows
      // from here can result in the BrowsingContext no longer
      // existing in the parent process by the time it deserializes
      // the IPC message.
    }
  }

  nsCOMPtr<nsIDocShellTreeItem> docShellAsItem = window->GetDocShell();
  // If there's no docShellAsItem, this window must have been closed,
  // in that case there is no tree owner.
  if (!docShellAsItem) {
    return;
  }

  // set this as the active window
  if (XRE_IsParentProcess()) {
    mActiveWindow = window;
  } else if (bc->IsTop()) {
    SetActiveBrowsingContextInContent(bc, aActionId,
                                      false /* aIsEnteringBFCache */);
  }

  // ensure that the window is enabled and visible
  nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
  docShellAsItem->GetTreeOwner(getter_AddRefs(treeOwner));
  if (nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner)) {
    bool isEnabled = true;
    if (NS_SUCCEEDED(baseWindow->GetEnabled(&isEnabled)) && !isEnabled) {
      return;
    }

    baseWindow->SetVisibility(true);
  }

  if (XRE_IsParentProcess()) {
    // Unsetting top-level focus upon lowering was inhibited to accommodate
    // ATOK, so we need to do it here.
    BrowserParent::UnsetTopLevelWebFocusAll();
    ActivateOrDeactivate(window, true);
  }

  // Retrieve the last focused element within the window that was raised.
  MoveFocusToWindowAfterRaise(window, aActionId);
}

void nsFocusManager::MoveFocusToWindowAfterRaise(nsPIDOMWindowOuter* aWindow,
                                                 uint64_t aActionId) {
  nsCOMPtr<nsPIDOMWindowOuter> currentWindow;
  RefPtr<Element> currentFocus = GetFocusedDescendant(
      aWindow, eIncludeAllDescendants, getter_AddRefs(currentWindow));

  NS_ASSERTION(currentWindow, "window raised with no window current");
  if (!currentWindow) {
    return;
  }

  // We use mFocusedWindow here is basically for the case that iframe navigate
  // from a.com to b.com for example, so it ends up being loaded in a different
  // process after Fission, but
  // currentWindow->GetBrowsingContext() == GetFocusedBrowsingContext() would
  // still be true because focused browsing context is synced, and we won't
  // fire a focus event while focusing if we use it as condition.
  Focus(currentWindow, currentFocus, /* aFlags = */ 0,
        /* aIsNewDocument = */ currentWindow != mFocusedWindow,
        /* aFocusChanged = */ false,
        /* aWindowRaised = */ true, /* aAdjustWidget = */ true, aActionId);
}

void nsFocusManager::WindowLowered(mozIDOMWindowProxy* aWindow,
                                   uint64_t aActionId) {
  if (!aWindow) {
    return;
  }

  nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);

  if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
    LOGFOCUS(("Window %p Lowered [Currently: %p %p]", aWindow,
              mActiveWindow.get(), mFocusedWindow.get()));
    Document* doc = window->GetExtantDoc();
    if (doc && doc->GetDocumentURI()) {
      LOGFOCUS((" Lowered Window: %s",
                doc->GetDocumentURI()->GetSpecOrDefault().get()));
    }
    if (mActiveWindow) {
      doc = mActiveWindow->GetExtantDoc();
      if (doc && doc->GetDocumentURI()) {
        LOGFOCUS((" Active Window: %s",
                  doc->GetDocumentURI()->GetSpecOrDefault().get()));
      }
    }
  }

  if (XRE_IsParentProcess()) {
    if (mActiveWindow != window) {
      return;
    }
  } else {
    BrowsingContext* bc = window->GetBrowsingContext();
    BrowsingContext* active = GetActiveBrowsingContext();
    if (active != bc->Top()) {
      return;
    }
  }

  // clear the mouse capture as the active window has changed
  PresShell::ReleaseCapturingContent();

  // In addition, reset the drag state to ensure that we are no longer in
  // drag-select mode.
  if (mFocusedWindow) {
    nsCOMPtr<nsIDocShell> docShell = mFocusedWindow->GetDocShell();
    if (docShell) {
      if (PresShell* presShell = docShell->GetPresShell()) {
        RefPtr<nsFrameSelection> frameSelection = presShell->FrameSelection();
        frameSelection->SetDragState(false);
      }
    }
  }

  if (XRE_IsParentProcess()) {
    ActivateOrDeactivate(window, false);
  }

  // keep track of the window being lowered, so that attempts to raise the
  // window can be prevented until we return. Otherwise, focus can get into
  // an unusual state.
  mWindowBeingLowered = window;
  if (XRE_IsParentProcess()) {
    mActiveWindow = nullptr;
  } else {
    BrowsingContext* bc = window->GetBrowsingContext();
    if (bc == bc->Top()) {
      SetActiveBrowsingContextInContent(nullptr, aActionId,
                                        false /* aIsEnteringBFCache */);
    }
  }

  if (mFocusedWindow) {
    Blur(nullptr, nullptr, truetruefalse, aActionId);
  }

  mWindowBeingLowered = nullptr;
}

nsresult nsFocusManager::ContentRemoved(Document* aDocument,
                                        nsIContent* aContent) {
  NS_ENSURE_ARG(aDocument);
  NS_ENSURE_ARG(aContent);

  nsPIDOMWindowOuter* windowPtr = aDocument->GetWindow();
  if (!windowPtr) {
    return NS_OK;
  }

  // if the content is currently focused in the window, or is an
  // shadow-including inclusive ancestor of the currently focused element,
  // reset the focus within that window.
  Element* previousFocusedElementPtr = windowPtr->GetFocusedElement();
  if (!previousFocusedElementPtr) {
    return NS_OK;
  }

  if (!nsContentUtils::ContentIsHostIncludingDescendantOf(
          previousFocusedElementPtr, aContent)) {
    return NS_OK;
  }

  RefPtr<nsPIDOMWindowOuter> window = windowPtr;
  RefPtr<Element> previousFocusedElement = previousFocusedElementPtr;

  RefPtr<Element> newFocusedElement = [&]() -> Element* {
    if (auto* sr = ShadowRoot::FromNode(aContent)) {
      if (sr->IsUAWidget() && sr->Host()->IsHTMLElement(nsGkAtoms::input)) {
        return sr->Host();
      }
    }
    return nullptr;
  }();

  window->SetFocusedElement(newFocusedElement);

  // if this window is currently focused, clear the global focused
  // element as well, but don't fire any events.
  if (window->GetBrowsingContext() == GetFocusedBrowsingContext()) {
    mFocusedElement = newFocusedElement;
  } else if (Document* subdoc =
                 aDocument->GetSubDocumentFor(previousFocusedElement)) {
    // Check if the node that was focused is an iframe or similar by looking if
    // it has a subdocument. This would indicate that this focused iframe
    // and its descendants will be going away. We will need to move the focus
    // somewhere else, so just clear the focus in the toplevel window so that no
    // element is focused.
    //
    // The Fission case is handled in FlushAndCheckIfFocusable().
    if (nsCOMPtr<nsIDocShell> docShell = subdoc->GetDocShell()) {
      nsCOMPtr<nsPIDOMWindowOuter> childWindow = docShell->GetWindow();
      if (childWindow &&
          IsSameOrAncestor(childWindow, GetFocusedBrowsingContext())) {
        if (XRE_IsParentProcess()) {
          nsCOMPtr<nsPIDOMWindowOuter> activeWindow = mActiveWindow;
          ClearFocus(activeWindow);
        } else {
          BrowsingContext* active = GetActiveBrowsingContext();
          if (active) {
            if (active->IsInProcess()) {
              nsCOMPtr<nsPIDOMWindowOuter> activeWindow =
                  active->GetDOMWindow();
              ClearFocus(activeWindow);
            } else {
              mozilla::dom::ContentChild* contentChild =
                  mozilla::dom::ContentChild::GetSingleton();
              MOZ_ASSERT(contentChild);
              contentChild->SendClearFocus(active);
            }
          }  // no else, because ClearFocus does nothing with nullptr
        }
      }
    }
  }

  // Notify the editor in case we removed its ancestor limiter.
  if (previousFocusedElement->IsEditable()) {
    if (nsIDocShell* const docShell = aDocument->GetDocShell()) {
      if (HTMLEditor* const htmlEditor = docShell->GetHTMLEditor()) {
        Selection* const selection = htmlEditor->GetSelection();
        if (selection && selection->GetFrameSelection() &&
            previousFocusedElement ==
                selection->GetFrameSelection()->GetAncestorLimiter()) {
          // The editing host may be being removed right now.  So, it's already
          // removed from the child chain of the parent node, but it still know
          // the parent node.  This could cause unexpected result at scheduling
          // paint of the caret.  Therefore, we should call FinalizeSelection
          // after unblocking to run the script.
          nsContentUtils::AddScriptRunner(
              NewRunnableMethod("HTMLEditor::FinalizeSelection", htmlEditor,
                                &HTMLEditor::FinalizeSelection));
        }
      }
    }
  }

  if (!newFocusedElement) {
    NotifyFocusStateChange(previousFocusedElement, newFocusedElement, 0,
                           /* aGettingFocus = */ false, false);
  } else {
    // We should already have the right state, which is managed by the <input>
    // widget.
    MOZ_ASSERT(newFocusedElement->State().HasState(ElementState::FOCUS));
  }

  // If we changed focused element and the element still has focus, let's
  // notify IME of focus.  Note that if new focus move has already occurred
  // by running script, we should not let IMEStateManager of outdated focus
  // change.
  if (mFocusedElement == newFocusedElement && mFocusedWindow == window) {
    RefPtr<nsPresContext> presContext(aDocument->GetPresContext());
    IMEStateManager::OnChangeFocus(presContext, newFocusedElement,
                                   InputContextAction::Cause::CAUSE_UNKNOWN);
  }

  return NS_OK;
}

void nsFocusManager::WindowShown(mozIDOMWindowProxy* aWindow,
                                 bool aNeedsFocus) {
  if (!aWindow) {
    return;
  }

  nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);

  if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
    LOGFOCUS(("Window %p Shown [Currently: %p %p]", window.get(),
              mActiveWindow.get(), mFocusedWindow.get()));
    Document* doc = window->GetExtantDoc();
    if (doc && doc->GetDocumentURI()) {
      LOGFOCUS(("Shown Window: %s",
                doc->GetDocumentURI()->GetSpecOrDefault().get()));
    }

    if (mFocusedWindow) {
      doc = mFocusedWindow->GetExtantDoc();
      if (doc && doc->GetDocumentURI()) {
        LOGFOCUS((" Focused Window: %s",
                  doc->GetDocumentURI()->GetSpecOrDefault().get()));
      }
    }
  }

  if (XRE_IsParentProcess()) {
    if (BrowsingContext* bc = window->GetBrowsingContext()) {
      if (bc->IsTop()) {
        bc->SetIsActiveBrowserWindow(bc->GetIsActiveBrowserWindow());
      }
    }
  }

  if (XRE_IsParentProcess()) {
    if (mFocusedWindow != window) {
      return;
    }
  } else {
    BrowsingContext* bc = window->GetBrowsingContext();
    if (!bc || mFocusedBrowsingContextInContent != bc) {
      return;
    }
    // Sync the window for a newly-created OOP iframe
    // Set actionId to zero to signify that it should be ignored.
    SetFocusedWindowInternal(window, 0, false);
  }

  if (aNeedsFocus) {
    nsCOMPtr<nsPIDOMWindowOuter> currentWindow;
    RefPtr<Element> currentFocus = GetFocusedDescendant(
        window, eIncludeAllDescendants, getter_AddRefs(currentWindow));

    if (currentWindow) {
      Focus(currentWindow, currentFocus, 0, truefalsefalsetrue,
            GenerateFocusActionId());
    }
  } else {
    // Sometimes, an element in a window can be focused before the window is
    // visible, which would mean that the widget may not be properly focused.
    // When the window becomes visible, make sure the right widget is focused.
    EnsureCurrentWidgetFocused(CallerType::System);
  }
}

void nsFocusManager::WindowHidden(mozIDOMWindowProxy* aWindow,
                                  uint64_t aActionId, bool aIsEnteringBFCache) {
  // if there is no window or it is not the same or an ancestor of the
  // currently focused window, just return, as the current focus will not
  // be affected.

  if (!aWindow) {
    return;
  }

  nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);

  if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
    LOGFOCUS(("Window %p Hidden [Currently: %p %p] actionid: %" PRIu64,
              window.get(), mActiveWindow.get(), mFocusedWindow.get(),
              aActionId));
    nsAutoCString spec;
    Document* doc = window->GetExtantDoc();
    if (doc && doc->GetDocumentURI()) {
      LOGFOCUS((" Hide Window: %s",
                doc->GetDocumentURI()->GetSpecOrDefault().get()));
    }

    if (mFocusedWindow) {
      doc = mFocusedWindow->GetExtantDoc();
      if (doc && doc->GetDocumentURI()) {
        LOGFOCUS((" Focused Window: %s",
                  doc->GetDocumentURI()->GetSpecOrDefault().get()));
      }
    }

    if (mActiveWindow) {
      doc = mActiveWindow->GetExtantDoc();
      if (doc && doc->GetDocumentURI()) {
        LOGFOCUS((" Active Window: %s",
                  doc->GetDocumentURI()->GetSpecOrDefault().get()));
      }
    }
  }

  if (!IsSameOrAncestor(window, mFocusedWindow)) {
    return;
  }

  // at this point, we know that the window being hidden is either the focused
  // window, or an ancestor of the focused window. Either way, the focus is no
  // longer valid, so it needs to be updated.

  const RefPtr<Element> oldFocusedElement = std::move(mFocusedElement);

  nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
  if (!focusedDocShell) {
    return;
  }

  const RefPtr<PresShell> presShell = focusedDocShell->GetPresShell();

  if (oldFocusedElement && oldFocusedElement->IsInComposedDoc()) {
    NotifyFocusStateChange(oldFocusedElement, nullptr, 0, falsefalse);
    window->UpdateCommands(u"focus"_ns);

    if (presShell) {
      RefPtr<Document> composedDoc = oldFocusedElement->GetComposedDoc();
      SendFocusOrBlurEvent(eBlur, presShell, composedDoc, oldFocusedElement,
                           false);
    }
  }

  const RefPtr<nsPresContext> focusedPresContext =
      presShell ? presShell->GetPresContext() : nullptr;
  IMEStateManager::OnChangeFocus(focusedPresContext, nullptr,
                                 GetFocusMoveActionCause(0));
  if (presShell) {
    SetCaretVisible(presShell, false, nullptr);
  }

  // If a window is being "hidden" because its BrowsingContext is changing
  // remoteness, we don't want to handle docshell destruction by moving focus.
  // Instead, the focused browsing context should stay the way it is (so that
  // the newly "shown" window in the other process knows to take focus) and
  // we should just null out the process-local field.
  nsCOMPtr<nsIDocShell> docShellBeingHidden = window->GetDocShell();
  // Check if we're currently hiding a non-remote nsDocShell due to its
  // BrowsingContext navigating to become remote. Normally, when a focused
  // subframe is hidden, focus is moved to the frame element, but focus should
  // stay with the BrowsingContext when performing a process switch. We don't
  // need to consider process switches where the hiding docshell is already
  // remote (ie. GetEmbedderElement is nullptr), as shifting remoteness to the
  // frame element is handled elsewhere.
  if (docShellBeingHidden &&
      nsDocShell::Cast(docShellBeingHidden)->WillChangeProcess() &&
      docShellBeingHidden->GetBrowsingContext()->GetEmbedderElement()) {
    if (mFocusedWindow != window) {
      // The window being hidden is an ancestor of the focused window.
#ifdef DEBUG
      BrowsingContext* ancestor = window->GetBrowsingContext();
      BrowsingContext* bc = mFocusedWindow->GetBrowsingContext();
      for (;;) {
        if (!bc) {
          MOZ_ASSERT(false"Should have found ancestor");
        }
        bc = bc->GetParent();
        if (ancestor == bc) {
          break;
        }
      }
#endif
      // This call adjusts the focused browsing context and window.
      // The latter gets nulled out immediately below.
      SetFocusedWindowInternal(window, aActionId);
    }
    mFocusedWindow = nullptr;
    window->SetFocusedElement(nullptr);
    return;
  }

  // if the docshell being hidden is being destroyed, then we want to move
  // focus somewhere else. Call ClearFocus on the toplevel window, which
  // will have the effect of clearing the focus and moving the focused window
  // to the toplevel window. But if the window isn't being destroyed, we are
  // likely just loading a new document in it, so we want to maintain the
  // focused window so that the new document gets properly focused.
  bool beingDestroyed = !docShellBeingHidden;
  if (docShellBeingHidden) {
    docShellBeingHidden->IsBeingDestroyed(&beingDestroyed);
  }
  if (beingDestroyed) {
    // There is usually no need to do anything if a toplevel window is going
    // away, as we assume that WindowLowered will be called. However, this may
    // not happen if nsIAppStartup::eForceQuit is used to quit, and can cause
    // a leak. So if the active window is being destroyed, call WindowLowered
    // directly.

    if (XRE_IsParentProcess()) {
      nsCOMPtr<nsPIDOMWindowOuter> activeWindow = mActiveWindow;
      if (activeWindow == mFocusedWindow || activeWindow == window) {
        WindowLowered(activeWindow, aActionId);
      } else {
        ClearFocus(activeWindow);
      }
    } else {
      BrowsingContext* active = GetActiveBrowsingContext();
      if (active) {
        if (nsCOMPtr<nsPIDOMWindowOuter> activeWindow =
                active->GetDOMWindow()) {
          if ((mFocusedWindow &&
               mFocusedWindow->GetBrowsingContext() == active) ||
              (window->GetBrowsingContext() == active)) {
            WindowLowered(activeWindow, aActionId);
          } else {
            ClearFocus(activeWindow);
          }
        }  // else do nothing when an out-of-process iframe is torn down
      }
    }
    return;
  }

  if (!XRE_IsParentProcess() &&
      mActiveBrowsingContextInContent ==
          docShellBeingHidden->GetBrowsingContext() &&
      mActiveBrowsingContextInContent->GetIsInBFCache()) {
    SetActiveBrowsingContextInContent(nullptr, aActionId, aIsEnteringBFCache);
  }

  // if the window being hidden is an ancestor of the focused window, adjust
  // the focused window so that it points to the one being hidden. This
  // ensures that the focused window isn't in a chain of frames that doesn't
  // exist any more.
  if (window != mFocusedWindow) {
    nsCOMPtr<nsIDocShellTreeItem> dsti =
        mFocusedWindow ? mFocusedWindow->GetDocShell() : nullptr;
    if (dsti) {
      nsCOMPtr<nsIDocShellTreeItem> parentDsti;
      dsti->GetInProcessParent(getter_AddRefs(parentDsti));
      if (parentDsti) {
        if (nsCOMPtr<nsPIDOMWindowOuter> parentWindow =
                parentDsti->GetWindow()) {
          parentWindow->SetFocusedElement(nullptr);
        }
      }
    }

    SetFocusedWindowInternal(window, aActionId);
  }
}

void nsFocusManager::FireDelayedEvents(Document* aDocument) {
  MOZ_ASSERT(aDocument);

  // fire any delayed focus and blur events in the same order that they were
  // added
  for (uint32_t i = 0; i < mDelayedBlurFocusEvents.Length(); i++) {
    if (mDelayedBlurFocusEvents[i].mDocument == aDocument) {
      if (!aDocument->GetInnerWindow() ||
          !aDocument->GetInnerWindow()->IsCurrentInnerWindow()) {
        // If the document was navigated away from or is defunct, don't bother
        // firing events on it. Note the symmetry between this condition and
        // the similar one in Document.cpp:FireOrClearDelayedEvents.
        mDelayedBlurFocusEvents.RemoveElementAt(i);
        --i;
      } else if (!aDocument->EventHandlingSuppressed()) {
        EventMessage message = mDelayedBlurFocusEvents[i].mEventMessage;
        nsCOMPtr<EventTarget> target = mDelayedBlurFocusEvents[i].mTarget;
        RefPtr<PresShell> presShell = mDelayedBlurFocusEvents[i].mPresShell;
        nsCOMPtr<EventTarget> relatedTarget =
            mDelayedBlurFocusEvents[i].mRelatedTarget;
        mDelayedBlurFocusEvents.RemoveElementAt(i);

        FireFocusOrBlurEvent(message, presShell, target, falsefalse,
                             relatedTarget);
        --i;
      }
    }
  }
}

void nsFocusManager::WasNuked(nsPIDOMWindowOuter* aWindow) {
  MOZ_ASSERT(aWindow, "Expected non-null window.");
  if (aWindow == mActiveWindow) {
    // TODO(emilio, bug 1933555): Figure out if we can assert below.
    // MOZ_ASSERT_UNREACHABLE("How come we're nuking a window that's still
    // active?");
    mActiveWindow = nullptr;
    SetActiveBrowsingContextInChrome(nullptr, GenerateFocusActionId());
  }
  if (aWindow == mFocusedWindow) {
    mFocusedWindow = nullptr;
    SetFocusedBrowsingContext(nullptr, GenerateFocusActionId());
    mFocusedElement = nullptr;
  }
}

nsFocusManager::BlurredElementInfo::BlurredElementInfo(Element& aElement)
    : mElement(aElement) {}

nsFocusManager::BlurredElementInfo::~BlurredElementInfo() = default;

// https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo
static bool ShouldMatchFocusVisible(nsPIDOMWindowOuter* aWindow,
                                    const Element& aElement,
                                    int32_t aFocusFlags) {
  // If we were explicitly requested to show the ring, do it.
  if (aFocusFlags & nsIFocusManager::FLAG_SHOWRING) {
    return true;
  }

  if (aFocusFlags & nsIFocusManager::FLAG_NOSHOWRING) {
    return false;
  }

  if (aWindow->ShouldShowFocusRing()) {
    // The window decision also trumps any other heuristic.
    return true;
  }

  // Any element which supports keyboard input (such as an input element, or any
  // other element which may trigger a virtual keyboard to be shown on focus if
  // a physical keyboard is not present) should always match :focus-visible when
  // focused.
  {
    if (aElement.IsHTMLElement(nsGkAtoms::textarea) || aElement.IsEditable()) {
      return true;
    }

    if (auto* input = HTMLInputElement::FromNode(aElement)) {
      if (input->IsSingleLineTextControl()) {
        return true;
      }
    }
  }

  switch (nsFocusManager::GetFocusMoveActionCause(aFocusFlags)) {
    case InputContextAction::CAUSE_KEY:
      // If the user interacts with the page via the keyboard, the currently
      // focused element should match :focus-visible (i.e. keyboard usage may
      // change whether this pseudo-class matches even if it doesn't affect
      // :focus).
      return true;
    case InputContextAction::CAUSE_UNKNOWN:
      // We render outlines if the last "known" focus method was by key or there
      // was no previous known focus method, otherwise we don't.
      return aWindow->UnknownFocusMethodShouldShowOutline();
    case InputContextAction::CAUSE_MOUSE:
    case InputContextAction::CAUSE_TOUCH:
    case InputContextAction::CAUSE_LONGPRESS:
      // If the user interacts with the page via a pointing device, such that
      // the focus is moved to a new element which does not support user input,
      // the newly focused element should not match :focus-visible.
      return false;
    case InputContextAction::CAUSE_UNKNOWN_CHROME:
    case InputContextAction::CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT:
    case InputContextAction::CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT:
      // TODO(emilio): We could return some of these though, looking at
      // UserActivation. We may want to suppress focus rings for unknown /
      // programatic focus if the user is interacting with the page but not
      // during keyboard input, or such.
      MOZ_ASSERT_UNREACHABLE(
          "These don't get returned by GetFocusMoveActionCause");
      break;
  }
  return false;
}

/* static */
void nsFocusManager::NotifyFocusStateChange(Element* aElement,
                                            Element* aElementToFocus,
                                            int32_t aFlags, bool aGettingFocus,
                                            bool aShouldShowFocusRing) {
  MOZ_ASSERT_IF(aElementToFocus, !aGettingFocus);
  nsIContent* commonAncestor = nullptr;
  if (aElementToFocus) {
    commonAncestor = nsContentUtils::GetCommonFlattenedTreeAncestor(
        aElement, aElementToFocus);
  }

  if (aGettingFocus) {
    ElementState stateToAdd = ElementState::FOCUS;
    if (aShouldShowFocusRing) {
      stateToAdd |= ElementState::FOCUSRING;
    }
    aElement->AddStates(stateToAdd);

    for (nsIContent* host = aElement->GetContainingShadowHost(); host;
         host = host->GetContainingShadowHost()) {
      host->AsElement()->AddStates(ElementState::FOCUS);
    }
  } else {
    constexpr auto kStatesToRemove =
        ElementState::FOCUS | ElementState::FOCUSRING;
    aElement->RemoveStates(kStatesToRemove);
    for (nsIContent* host = aElement->GetContainingShadowHost(); host;
         host = host->GetContainingShadowHost()) {
      host->AsElement()->RemoveStates(kStatesToRemove);
    }
  }

  // Special case for <input type="checkbox"> and <input type="radio">.
  // The other browsers cancel active state when they gets lost focus, but
  // does not do it for the other elements such as <button> and <a href="...">.
  // Additionally, they may be activated with <label>, but they will get focus
  // at `click`, but activated at `mousedown`.  Therefore, we need to cancel
  // active state at moving focus.
  if (RefPtr<nsPresContext> presContext =
          aElement->GetPresContext(Element::PresContextFor::eForComposedDoc)) {
    RefPtr<EventStateManager> esm = presContext->EventStateManager();
    auto* activeInputElement =
        HTMLInputElement::FromNodeOrNull(esm->GetActiveContent());
    if (activeInputElement &&
        (activeInputElement->ControlType() == FormControlType::InputCheckbox ||
         activeInputElement->ControlType() == FormControlType::InputRadio) &&
        !activeInputElement->State().HasState(ElementState::FOCUS)) {
      esm->SetContentState(nullptr, ElementState::ACTIVE);
    }
  }

  for (nsIContent* content = aElement; content && content != commonAncestor;
       content = content->GetFlattenedTreeParent()) {
    Element* element = Element::FromNode(content);
    if (!element) {
      continue;
    }

    if (aGettingFocus) {
      if (element->State().HasState(ElementState::FOCUS_WITHIN)) {
        break;
      }
      element->AddStates(ElementState::FOCUS_WITHIN);
    } else {
      element->RemoveStates(ElementState::FOCUS_WITHIN);
    }
  }
}

// static
void nsFocusManager::EnsureCurrentWidgetFocused(CallerType aCallerType) {
  if (!mFocusedWindow || sTestMode) return;

  // get the main child widget for the focused window and ensure that the
  // platform knows that this widget is focused.
  nsCOMPtr<nsIDocShell> docShell = mFocusedWindow->GetDocShell();
  if (!docShell) {
    return;
  }
  RefPtr<PresShell> presShell = docShell->GetPresShell();
  if (!presShell) {
    return;
  }
  nsViewManager* vm = presShell->GetViewManager();
  if (!vm) {
    return;
  }
  nsCOMPtr<nsIWidget> widget = vm->GetRootWidget();
  if (!widget) {
    return;
  }
  widget->SetFocus(nsIWidget::Raise::No, aCallerType);
}

void nsFocusManager::ActivateOrDeactivate(nsPIDOMWindowOuter* aWindow,
                                          bool aActive) {
  MOZ_ASSERT(XRE_IsParentProcess());
  if (!aWindow) {
    return;
  }

  if (BrowsingContext* bc = aWindow->GetBrowsingContext()) {
    MOZ_ASSERT(bc->IsTop());

    RefPtr<CanonicalBrowsingContext> chromeTop =
        bc->Canonical()->TopCrossChromeBoundary();
    MOZ_ASSERT(bc == chromeTop);

    chromeTop->SetIsActiveBrowserWindow(aActive);
    chromeTop->CallOnAllTopDescendants(
        [aActive](CanonicalBrowsingContext* aBrowsingContext) {
          aBrowsingContext->SetIsActiveBrowserWindow(aActive);
          return CallState::Continue;
        },
        /* aIncludeNestedBrowsers = */ true);
  }

  if (aWindow->GetExtantDoc()) {
    nsContentUtils::DispatchEventOnlyToChrome(
        aWindow->GetExtantDoc(),
        nsGlobalWindowInner::Cast(aWindow->GetCurrentInnerWindow()),
        aActive ? u"activate"_ns : u"deactivate"_ns, CanBubble::eYes,
        Cancelable::eYes, nullptr);
  }
}

// Retrieves innerWindowId of the window of the last focused element to
// log a warning to the website console.
void LogWarningFullscreenWindowRaise(Element* aElement) {
  nsCOMPtr<nsFrameLoaderOwner> frameLoaderOwner(do_QueryInterface(aElement));
  NS_ENSURE_TRUE_VOID(frameLoaderOwner);

  RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader();
  NS_ENSURE_TRUE_VOID(frameLoaderOwner);

  RefPtr<BrowsingContext> browsingContext = frameLoader->GetBrowsingContext();
  NS_ENSURE_TRUE_VOID(browsingContext);

  WindowGlobalParent* windowGlobalParent =
      browsingContext->Canonical()->GetCurrentWindowGlobal();
  NS_ENSURE_TRUE_VOID(windowGlobalParent);

  // Log to console
  nsAutoString localizedMsg;
  nsTArray<nsString> params;
  nsresult rv = nsContentUtils::FormatLocalizedString(
      nsContentUtils::eDOM_PROPERTIES, "FullscreenExitWindowFocus", params,
      localizedMsg);

  NS_ENSURE_SUCCESS_VOID(rv);

  Unused << nsContentUtils::ReportToConsoleByWindowID(
      localizedMsg, nsIScriptError::warningFlag, "DOM"_ns,
      windowGlobalParent->InnerWindowId(),
      SourceLocation(windowGlobalParent->GetDocumentURI()));
}

// Ensure that when an embedded popup with a noautofocus attribute
// like a date picker is opened and focused, the parent page does not blur
static bool IsEmeddededInNoautofocusPopup(BrowsingContext& aBc) {
  auto* embedder = aBc.GetEmbedderElement();
  if (!embedder) {
    return false;
  }
  nsIFrame* f = embedder->GetPrimaryFrame();
  if (!f || !f->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
    return false;
  }

  nsIFrame* menuPopup =
      nsLayoutUtils::GetClosestFrameOfType(f, LayoutFrameType::MenuPopup);
  MOZ_ASSERT(menuPopup, "NS_FRAME_IN_POPUP lied?");
  return static_cast<nsMenuPopupFrame*>(menuPopup)
      ->PopupElement()
      .GetXULBoolAttr(nsGkAtoms::noautofocus);
}

Maybe<uint64_t> nsFocusManager::SetFocusInner(Element* aNewContent,
                                              int32_t aFlags,
                                              bool aFocusChanged,
                                              bool aAdjustWidget) {
  // if the element is not focusable, just return and leave the focus as is
  RefPtr<Element> elementToFocus =
      FlushAndCheckIfFocusable(aNewContent, aFlags);
  if (!elementToFocus) {
    return Nothing();
  }

  const RefPtr<BrowsingContext> focusedBrowsingContext =
      GetFocusedBrowsingContext();

  // check if the element to focus is a frame (iframe) containing a child
  // document. Frames are never directly focused; instead focusing a frame
  // means focus what is inside the frame. To do this, the descendant content
  // within the frame is retrieved and that will be focused instead.
  nsCOMPtr<nsPIDOMWindowOuter> newWindow;
  nsCOMPtr<nsPIDOMWindowOuter> subWindow = GetContentWindow(elementToFocus);
  if (subWindow) {
    elementToFocus = GetFocusedDescendant(subWindow, eIncludeAllDescendants,
                                          getter_AddRefs(newWindow));

    // since a window is being refocused, clear aFocusChanged so that the
    // caret position isn't updated.
    aFocusChanged = false;
  }

  // unless it was set above, retrieve the window for the element to focus
  if (!newWindow) {
    newWindow = GetCurrentWindow(elementToFocus);
  }

  RefPtr<BrowsingContext> newBrowsingContext;
  if (newWindow) {
    newBrowsingContext = newWindow->GetBrowsingContext();
  }

  // if the element is already focused, just return. Note that this happens
  // after the frame check above so that we compare the element that will be
  // focused rather than the frame it is in.
  if (!newWindow || (newBrowsingContext == GetFocusedBrowsingContext() &&
                     elementToFocus == mFocusedElement)) {
    return Nothing();
  }

  MOZ_ASSERT(newBrowsingContext);

  BrowsingContext* browsingContextToFocus = newBrowsingContext;
  if (RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(elementToFocus)) {
    // Only look at pre-existing browsing contexts. If this function is
    // called during reflow, calling GetBrowsingContext() could cause frame
    // loader initialization at a time when it isn't safe.
    if (BrowsingContext* bc = flo->GetExtantBrowsingContext()) {
      // If focus is already in the subtree rooted at bc, return early
      // to match the single-process focus semantics. Otherwise, we'd
      // blur and immediately refocus whatever is focused.
      BrowsingContext* walk = focusedBrowsingContext;
      while (walk) {
        if (walk == bc) {
          return Nothing();
        }
        walk = walk->GetParent();
      }
      browsingContextToFocus = bc;
    }
  }

  // don't allow focus to be placed in docshells or descendants of docshells
  // that are being destroyed. Also, ensure that the page hasn't been
  // unloaded. The prevents content from being refocused during an unload event.
  nsCOMPtr<nsIDocShell> newDocShell = newWindow->GetDocShell();
  nsCOMPtr<nsIDocShell> docShell = newDocShell;
  while (docShell) {
    bool inUnload;
    docShell->GetIsInUnload(&inUnload);
    if (inUnload) {
      return Nothing();
    }

    bool beingDestroyed;
    docShell->IsBeingDestroyed(&beingDestroyed);
    if (beingDestroyed) {
      return Nothing();
    }

    BrowsingContext* bc = docShell->GetBrowsingContext();

    nsCOMPtr<nsIDocShellTreeItem> parentDsti;
    docShell->GetInProcessParent(getter_AddRefs(parentDsti));
    docShell = do_QueryInterface(parentDsti);
    if (!docShell && !XRE_IsParentProcess()) {
      // We don't have an in-process parent, but let's see if we have
      // an in-process ancestor or if an out-of-process ancestor
      // is discarded.
      do {
        bc = bc->GetParent();
        if (bc && bc->IsDiscarded()) {
          return Nothing();
        }
      } while (bc && !bc->IsInProcess());
      if (bc) {
        docShell = bc->GetDocShell();
      } else {
        docShell = nullptr;
      }
    }
  }

  bool focusMovesToDifferentBC =
      (focusedBrowsingContext != browsingContextToFocus);

  if (focusedBrowsingContext && focusMovesToDifferentBC &&
      nsContentUtils::IsHandlingKeyBoardEvent() &&
      !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
    MOZ_ASSERT(browsingContextToFocus,
               "BrowsingContext to focus should be non-null.");

    nsIPrincipal* focusedPrincipal = nullptr;
    nsIPrincipal* newPrincipal = nullptr;

    if (XRE_IsParentProcess()) {
      if (WindowGlobalParent* focusedWindowGlobalParent =
              focusedBrowsingContext->Canonical()->GetCurrentWindowGlobal()) {
        focusedPrincipal = focusedWindowGlobalParent->DocumentPrincipal();
      }

      if (WindowGlobalParent* newWindowGlobalParent =
              browsingContextToFocus->Canonical()->GetCurrentWindowGlobal()) {
        newPrincipal = newWindowGlobalParent->DocumentPrincipal();
      }
    } else if (focusedBrowsingContext->IsInProcess() &&
               browsingContextToFocus->IsInProcess()) {
      nsCOMPtr<nsIScriptObjectPrincipal> focused =
          do_QueryInterface(focusedBrowsingContext->GetDOMWindow());
      nsCOMPtr<nsIScriptObjectPrincipal> newFocus =
          do_QueryInterface(browsingContextToFocus->GetDOMWindow());
      MOZ_ASSERT(focused && newFocus,
                 "BrowsingContext should always have a window here.");
      focusedPrincipal = focused->GetPrincipal();
      newPrincipal = newFocus->GetPrincipal();
    }

    if (!focusedPrincipal || !newPrincipal) {
      return Nothing();
    }

    if (!focusedPrincipal->Subsumes(newPrincipal)) {
      NS_WARNING("Not allowed to focus the new window!");
      return Nothing();
    }
  }

  // to check if the new element is in the active window, compare the
  // new root docshell for the new element with the active window's docshell.
  RefPtr<BrowsingContext> newRootBrowsingContext = nullptr;
  bool isElementInActiveWindow = false;
  if (XRE_IsParentProcess()) {
    nsCOMPtr<nsPIDOMWindowOuter> newRootWindow = nullptr;
    nsCOMPtr<nsIDocShellTreeItem> dsti = newWindow->GetDocShell();
    if (dsti) {
      nsCOMPtr<nsIDocShellTreeItem> root;
      dsti->GetInProcessRootTreeItem(getter_AddRefs(root));
      newRootWindow = root ? root->GetWindow() : nullptr;

      isElementInActiveWindow =
          (mActiveWindow && newRootWindow == mActiveWindow);
    }
    if (newRootWindow) {
      newRootBrowsingContext = newRootWindow->GetBrowsingContext();
    }
  } else {
    // XXX This is wrong for `<iframe mozbrowser>` and for XUL
    // `<browser remote="true">`. See:
    // https://searchfox.org/mozilla-central/rev/8a63fc190b39ed6951abb4aef4a56487a43962bc/dom/base/nsFrameLoader.cpp#229-232
    newRootBrowsingContext = newBrowsingContext->Top();
    // to check if the new element is in the active window, compare the
    // new root docshell for the new element with the active window's docshell.
    isElementInActiveWindow =
        (GetActiveBrowsingContext() == newRootBrowsingContext);
  }

  // Exit fullscreen if a website focuses another window
  if (StaticPrefs::full_screen_api_exit_on_windowRaise() &&
      !isElementInActiveWindow && (aFlags & FLAG_RAISE)) {
    if (XRE_IsParentProcess()) {
      if (Document* doc = mActiveWindow ? mActiveWindow->GetDoc() : nullptr) {
        Document::ClearPendingFullscreenRequests(doc);
        if (doc->GetFullscreenElement()) {
          LogWarningFullscreenWindowRaise(mFocusedElement);
          Document::AsyncExitFullscreen(doc);
        }
      }
    } else {
      BrowsingContext* activeBrowsingContext = GetActiveBrowsingContext();
      if (activeBrowsingContext) {
        nsIDocShell* shell = activeBrowsingContext->GetDocShell();
        if (shell) {
          if (Document* doc = shell->GetDocument()) {
            Document::ClearPendingFullscreenRequests(doc);
            if (doc->GetFullscreenElement()) {
              Document::AsyncExitFullscreen(doc);
            }
          }
        } else {
          mozilla::dom::ContentChild* contentChild =
              mozilla::dom::ContentChild::GetSingleton();
          MOZ_ASSERT(contentChild);
          contentChild->SendMaybeExitFullscreen(activeBrowsingContext);
        }
      }
    }
  }

  // if the FLAG_NOSWITCHFRAME flag is used, only allow the focus to be
  // shifted away from the current element if the new shell to focus is
  // the same or an ancestor shell of the currently focused shell.
  bool allowFrameSwitch = !(aFlags & FLAG_NOSWITCHFRAME) ||
                          IsSameOrAncestor(newWindow, focusedBrowsingContext);

  // if the element is in the active window, frame switching is allowed and
  // the content is in a visible window, fire blur and focus events.
  bool sendFocusEvent =
      isElementInActiveWindow && allowFrameSwitch && IsWindowVisible(newWindow);

  // Don't allow to steal the focus from chrome nodes if the caller cannot
  // access them.
  if (sendFocusEvent && mFocusedElement &&
      mFocusedElement->OwnerDoc() != aNewContent->OwnerDoc() &&
      mFocusedElement->NodePrincipal()->IsSystemPrincipal() &&
      !nsContentUtils::LegacyIsCallerNativeCode() &&
      !nsContentUtils::CanCallerAccess(mFocusedElement)) {
    sendFocusEvent = false;
  }

  LOGCONTENT("Shift Focus: %s", elementToFocus.get());
  LOGFOCUS((" Flags: %x Current Window: %p New Window: %p Current Element: %p",
            aFlags, mFocusedWindow.get(), newWindow.get(),
            mFocusedElement.get()));
  const uint64_t actionId = GenerateFocusActionId();
  LOGFOCUS(
      (" In Active Window: %d Moves to different BrowsingContext: %d "
       "SendFocus: %d actionid: %" PRIu64,
       isElementInActiveWindow, focusMovesToDifferentBC, sendFocusEvent,
       actionId));

  if (sendFocusEvent) {
    Maybe<BlurredElementInfo> blurredInfo;
    if (mFocusedElement) {
--> --------------------

--> maximum size reached

--> --------------------

97%


¤ Dauer der Verarbeitung: 0.9 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.