Low chrome Race 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactRace condition in Editing
DescriptionRace condition in Editing
ComponentEditing
Bug ClassRace
Tracker502252964
Fix commit4217ca7b76c7 (chromium/src) +39/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Changed Functions

FunctionChangeNotes
TEST_P
third_party/blink/renderer/core/editing/spellcheck/idle_spell_check_controller_test.cc
modified

Files Changed

  • third_party/blink/renderer/core/editing/frame_selection.cc
  • third_party/blink/renderer/core/editing/spellcheck/idle_spell_check_controller_test.cc
From 4217ca7b76c7d688e5d0afddacb93128c1baffd6 Mon Sep 17 00:00:00 2001
From: Stefan Zager <[email protected]>
Date: Wed, 08 Jul 2026 15:12:23 -0700
Subject: [PATCH] Verify new focused selection before triggering spell checker

The previous call site to RespondToChangedSelection() was _before_ the
DOM selection update had been applied.

Bug: 502252964
Change-Id: I2852df5e545cd37abeda19c051f61c32962bb08d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8063807
Commit-Queue: Stefan Zager <[email protected]>
Reviewed-by: Philip Rogers <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1659095}
---

diff --git a/third_party/blink/renderer/core/editing/frame_selection.cc b/third_party/blink/renderer/core/editing/frame_selection.cc
index 0ada1e6..175c5a8a 100644
--- a/third_party/blink/renderer/core/editing/frame_selection.cc
+++ b/third_party/blink/renderer/core/editing/frame_selection.cc
@@ -329,7 +329,6 @@
   is_handle_visible_ = should_show_handle;
   ScheduleVisualUpdateForVisualOverflowIfNeeded();
 
-  frame_->GetEditor().RespondToChangedSelection();
   DCHECK_EQ(current_document, GetDocument());
   return true;
 }
@@ -432,6 +431,7 @@
         *Event::Create(event_type_names::kSelectionchange),
         TaskType::kMiscPlatformAPI);
   }
+  frame_->GetEditor().RespondToChangedSelection();
 }
 
 void FrameSelection::SetSelectionForAccessibility(
diff --git a/third_party/blink/renderer/core/editing/spellcheck/idle_spell_check_controller_test.cc b/third_party/blink/renderer/core/editing/spellcheck/idle_spell_check_controller_test.cc
index 0de18d91..5379da5 100644
--- a/third_party/blink/renderer/core/editing/spellcheck/idle_spell_check_controller_test.cc
+++ b/third_party/blink/renderer/core/editing/spellcheck/idle_spell_check_controller_test.cc
@@ -334,4 +334,42 @@
   }
 }
 
+TEST_P(IdleSpellCheckControllerTest, ProgrammaticSelectionChangeToNewElement) {
+  IdleChecker().Deactivate();
+  SetBodyContent(
+      "<div id='div1' contenteditable='true' spellcheck='true'>foo</div>"
+      "<div id='div2' contenteditable='true' spellcheck='true'>bar</div>");
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_EQ(State::kInactive, IdleChecker().GetState());
+
+  // Focus div1 using a mouse user gesture.
+  Element* div1 = QuerySelector("#div1");
+  ASSERT_NE(div1, nullptr);
+  div1->Focus(FocusParams(SelectionBehaviorOnFocus::kRestore,
+                          mojom::blink::FocusType::kMouse,
+                          /*capabilities=*/nullptr));
+  GetDocument().GetFrame()->Selection().SetFrameIsFocused(true);
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_EQ(State::kHotModeRequested, IdleChecker().GetState());
+  EXPECT_EQ(div1, GetDocument().FocusedElement());
+  EXPECT_TRUE(div1->WasLastFocusFromUserGesture());
+
+  // Programmatically change selection to div2 (without user gesture).
+  Element* div2 = QuerySelector("#div2");
+  ASSERT_NE(div2, nullptr);
+  EXPECT_FALSE(div2->WasLastFocusFromUserGesture());
+
+  GetDocument().GetFrame()->Selection().SetSelection(
+      SelectionInDomTree::Builder().Collapse(Position(div2, 0)).Build(),
+      SetSelectionOptions());
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_EQ(div2, GetDocument().FocusedElement());
+  if (IsUnrestricted()) {
+    EXPECT_EQ(State::kHotModeRequested, IdleChecker().GetState());
+  } else {
+    EXPECT_EQ(State::kInactive, IdleChecker().GetState());
+  }
+}
+
 }  // namespace blink
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/renderer/core/editing/spellcheck/idle_spell_check_controller_test.cc b/third_party/blink/renderer/core/editing/spellcheck/idle_spell_check_controller_test.cc
index 0de18d91..5379da5 100644
--- a/third_party/blink/renderer/core/editing/spellcheck/idle_spell_check_controller_test.cc
+++ b/third_party/blink/renderer/core/editing/spellcheck/idle_spell_check_controller_test.cc
@@ -334,4 +334,42 @@
   }
 }
 
+TEST_P(IdleSpellCheckControllerTest, ProgrammaticSelectionChangeToNewElement) {
+  IdleChecker().Deactivate();
+  SetBodyContent(
+      "<div id='div1' contenteditable='true' spellcheck='true'>foo</div>"
+      "<div id='div2' contenteditable='true' spellcheck='true'>bar</div>");
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_EQ(State::kInactive, IdleChecker().GetState());
+
+  // Focus div1 using a mouse user gesture.
+  Element* div1 = QuerySelector("#div1");
+  ASSERT_NE(div1, nullptr);
+  div1->Focus(FocusParams(SelectionBehaviorOnFocus::kRestore,
+                          mojom::blink::FocusType::kMouse,
+                          /*capabilities=*/nullptr));
+  GetDocument().GetFrame()->Selection().SetFrameIsFocused(true);
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_EQ(State::kHotModeRequested, IdleChecker().GetState());
+  EXPECT_EQ(div1, GetDocument().FocusedElement());
+  EXPECT_TRUE(div1->WasLastFocusFromUserGesture());
+
+  // Programmatically change selection to div2 (without user gesture).
+  Element* div2 = QuerySelector("#div2");
+  ASSERT_NE(div2, nullptr);
+  EXPECT_FALSE(div2->WasLastFocusFromUserGesture());
+
+  GetDocument().GetFrame()->Selection().SetSelection(
+      SelectionInDomTree::Builder().Collapse(Position(div2, 0)).Build(),
+      SetSelectionOptions());
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_EQ(div2, GetDocument().FocusedElement());
+  if (IsUnrestricted()) {
+    EXPECT_EQ(State::kHotModeRequested, IdleChecker().GetState());
+  } else {
+    EXPECT_EQ(State::kInactive, IdleChecker().GetState());
+  }
+}
+
 }  // namespace blink
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.