commit 64683992ff9726072627514179101359e6876d67
parent 7a3fd8df5061232e9d7e78acb1b5703da40e17cd
Author: Alexandru Marc <amarc@mozilla.com>
Date: Mon, 27 Oct 2025 19:08:14 +0200
Revert "Bug 1996182 - Fix up focus within state on focus redirect as well. r=smaug" for causing mochitest failures @ HTMLEditor.cpp
This reverts commit 6a2fbb9803ba82a9bcb1dcb64e96556b9e55fae7.
Revert "Bug 1996182 - Clear stale focus-within state from ContentRemoved. r=smaug"
This reverts commit 99b320901a54318b2fb25abaad74a79678d09fac.
Diffstat:
3 files changed, 26 insertions(+), 143 deletions(-)
diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp
@@ -8,7 +8,6 @@
#include <algorithm>
-#include "AncestorIterator.h"
#include "BrowserChild.h"
#include "ChildIterator.h"
#include "ContentParent.h"
@@ -911,45 +910,6 @@ void nsFocusManager::ContentAppended(nsIContent* aFirstNewContent,
FocusedElementMayHaveMoved(aFirstNewContent, aInfo.mOldParent);
}
-static void UpdateFocusWithinState(Element* aElement,
- nsIContent* aCommonAncestor,
- bool aGettingFocus) {
- for (nsIContent* content = aElement; content && content != aCommonAncestor;
- 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 MaybeFixUpFocusWithinState(Element* aElementToFocus,
- Element* aFocusedElement) {
- if (!aElementToFocus || aElementToFocus == aFocusedElement ||
- !aElementToFocus->IsInComposedDoc()) {
- return;
- }
- // Focus was redirected, make sure the :focus-within state remains consistent.
- auto* commonAncestor = [&]() -> nsIContent* {
- if (!aFocusedElement ||
- aElementToFocus->OwnerDoc() != aFocusedElement->OwnerDoc()) {
- return nullptr;
- }
- return nsContentUtils::GetCommonFlattenedTreeAncestor(aFocusedElement,
- aElementToFocus);
- }();
- UpdateFocusWithinState(aElementToFocus, commonAncestor, false);
-}
-
nsresult nsFocusManager::ContentRemoved(Document* aDocument,
nsIContent* aContent,
const ContentRemoveInfo& aInfo) {
@@ -966,37 +926,22 @@ nsresult nsFocusManager::ContentRemoved(Document* aDocument,
return NS_OK;
}
- Element* focusWithinElement = [&]() -> Element* {
- if (auto* el = Element::FromNode(aContent)) {
- return el;
- }
- if (auto* shadow = ShadowRoot::FromNode(aContent)) {
- // Note that we only get here with ShadowRoots for shadow roots of form
- // controls that we can un-attach. So if there's a focused element it must
- // be inside our shadow tree already.
- return shadow->Host();
- }
- // Removing text / comments / etc can't affect the focus state.
- return nullptr;
- }();
- if (!focusWithinElement ||
- !focusWithinElement->State().HasAtLeastOneOfStates(
- ElementState::FOCUS | ElementState::FOCUS_WITHIN)) {
- 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.
- RefPtr previousFocusedElement = windowPtr->GetFocusedElement();
- if (!previousFocusedElement) {
- // If we're in-between a blur and an incoming focus, we might have stale
- // :focus-within in our ancestor chain. Fix it up now.
- UpdateFocusWithinState(focusWithinElement, nullptr, false);
+ 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)) {
@@ -1069,7 +1014,7 @@ nsresult nsFocusManager::ContentRemoved(Document* aDocument,
}
if (!newFocusedElement) {
- NotifyFocusStateChange(previousFocusedElement, nullptr, 0,
+ NotifyFocusStateChange(previousFocusedElement, newFocusedElement, 0,
/* aGettingFocus = */ false, false);
} else {
// We should already have the right state, which is managed by the <input>
@@ -1511,7 +1456,22 @@ void nsFocusManager::NotifyFocusStateChange(Element* aElement,
}
}
- UpdateFocusWithinState(aElement, commonAncestor, aGettingFocus);
+ 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
@@ -1917,7 +1877,6 @@ Maybe<uint64_t> nsFocusManager::SetFocusInner(Element* aNewContent,
: nullptr),
commonAncestor, focusMovesToDifferentBC, aAdjustWidget,
remainActive, actionId, elementToFocus)) {
- MaybeFixUpFocusWithinState(elementToFocus, mFocusedElement);
return Some(actionId);
}
}
@@ -2869,9 +2828,6 @@ void nsFocusManager::Focus(
}
}
} else {
- // We only need this on this branch, on the branch above
- // NotifyFocusStateChange takes care of it.
- MaybeFixUpFocusWithinState(elementToFocus, mFocusedElement);
if (!mFocusedElement && mFocusedWindow == aWindow) {
// When there is no focused element, IMEStateManager needs to adjust IME
// enabled state with the document.
diff --git a/testing/web-platform/tests/css/selectors/focus-within-focus-move.html b/testing/web-platform/tests/css/selectors/focus-within-focus-move.html
@@ -1,37 +0,0 @@
-<!doctype html>
-<meta charset="utf-8">
-<title>Moving focus from its own focus() call doesn't leave stale :focus-within state</title>
-<link rel="help" href="https://drafts.csswg.org/selectors-4/#the-focus-within-pseudo">
-<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1996182">
-<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
-<link rel="author" href="https://mozilla.com" title="Mozilla">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<div id="wrapper">
- <div id="outer">
- <input id="tab">
- <input id="input" onblur="outside.focus()">
- </div>
- <input id="outside">
-</div>
-<script>
-onload = function() {
- test(function() {
- let wrapper = document.getElementById("wrapper");
- let outer = document.getElementById("outer");
- let tab = document.getElementById("tab");
- let input = document.getElementById("input");
- let outside = document.getElementById("outside");
-
- input.focus();
- assert_equals(document.activeElement, input, "activeElement after focus");
- assert_true(outer.matches(":focus-within"), "outer matches :focus-within");
- assert_true(wrapper.matches(":focus-within"), "wrapper matches :focus-within");
- // This ends up shifting to `outside` rather than `tab`.
- tab.focus();
- assert_equals(document.activeElement, outside, "activeElement after trying to focus sibling");
- assert_true(wrapper.matches(":focus-within"), "wrapper still matches :focus-within");
- assert_false(outer.matches(":focus-within"), "outer no longer matches :focus-within");
- });
-}
-</script>
diff --git a/testing/web-platform/tests/css/selectors/focus-within-removal.html b/testing/web-platform/tests/css/selectors/focus-within-removal.html
@@ -1,36 +0,0 @@
-<!doctype html>
-<meta charset="utf-8">
-<title>Removing an element from its own focus() call doesn't leave stale :focus-within state</title>
-<link rel="help" href="https://drafts.csswg.org/selectors-4/#the-focus-within-pseudo">
-<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1996182">
-<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
-<link rel="author" href="https://mozilla.com" title="Mozilla">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<div id="root">
- <div id="container" tabindex="-1">
- <input type="text">
- </div>
-</div>
-<script>
-onload = function() {
- test(function() {
- let root = document.getElementById("root");
- let container = document.getElementById("container");
- let input = document.querySelector("input");
-
- input.focus();
- assert_equals(document.activeElement, input, "activeElement after focus");
- assert_true(container.matches(":focus-within"), "container matches :focus-within");
- assert_true(root.matches(":focus-within"), "root also matches :focus-within");
- // This fires from within the next focus() call
- input.addEventListener("focusout", () => { root.innerHTML = "" });
- // container is focusable, but gets removed before we get a chance at focusing it.
- container.focus();
- assert_equals(document.activeElement, document.body, "activeElement after trying to focus sibling");
- assert_equals(container.parentNode, null, "container should get removed");
- assert_false(container.matches(":focus-within"), "container no longer matches :focus-within");
- assert_false(root.matches(":focus-within"), "root no longer matches :focus-within");
- });
-}
-</script>