Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Scheduling
DescriptionInappropriate implementation in Scheduling
ComponentScheduling
Bug ClassLogic Error
Tracker517436171
Fix commit6c4922974ad2 (chromium/src) +73/-3
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
TEST_F
third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
modified
TEST_F
third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_impl_unittest.cc
modified

Files Changed

  • third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
  • third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_impl.cc
  • third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_impl_unittest.cc
From 6c4922974ad2d71f2c139464ea35ce8b5715e9ca Mon Sep 17 00:00:00 2001
From: Scott Haseley <shaseley@chromium.org>
Date: Fri, 26 Jun 2026 12:33:49 -0700
Subject: [PATCH] [scheduler] Fix Worker QueueEnabledVoter init for web scheduling queues

This CL ensures web scheduling queues that are created while the worker
scheduler is paused are initially paused. This is only an issue if
self.scheduler APIs are called while paused, which typically should not
happen while paused (since JS should be running). But there could be
races with web locks task while paused in devtools, for example, that
could cause this. Also, the same hooks for aborting script or pausing
microtasks for BFCache aren't present on Workers, so this at least
ensures tasks scheduled via self.scheduler won't continue to run.

Note: this was already handled on the main thread, and this CL adds a
unit test for that case as well.

Fixed: 517436171
Change-Id: I6456e234cfbdf08e7f175368fb089e020b767bac
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8007687
Reviewed-by: Etienne Pierre-Doray <etiennep@chromium.org>
Commit-Queue: Scott Haseley <shaseley@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1653366}
---

diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
index 5d751c96..808b6f2 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
@@ -895,6 +895,13 @@
   UnpausableTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
       FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
 
+  std::unique_ptr<WebSchedulingTaskQueue> web_scheduling_task_queue =
+      frame_scheduler_->CreateWebSchedulingTaskQueue(
+          WebSchedulingQueueType::kTaskQueue,
+          WebSchedulingPriority::kUserVisiblePriority);
+  web_scheduling_task_queue->GetTaskRunner()->PostTask(
+      FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
+
   frame_scheduler_->SetPaused(true);
 
   EXPECT_EQ(0, counter);
@@ -905,7 +912,30 @@
 
   EXPECT_EQ(1, counter);
   base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(5, counter);
+  EXPECT_EQ(6, counter);
+}
+
+TEST_F(FrameSchedulerImplTest, PauseBeforeWebSchedulingQueueCreated) {
+  int counter = 0;
+
+  frame_scheduler_->SetPaused(true);
+
+  std::unique_ptr<WebSchedulingTaskQueue> web_scheduling_task_queue =
+      frame_scheduler_->CreateWebSchedulingTaskQueue(
+          WebSchedulingQueueType::kTaskQueue,
+          WebSchedulingPriority::kUserVisiblePriority);
+  web_scheduling_task_queue->GetTaskRunner()->PostTask(
+      FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
+
+  EXPECT_EQ(0, counter);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(0, counter);
+
+  frame_scheduler_->SetPaused(false);
+
+  EXPECT_EQ(0, counter);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(1, counter);
 }
 
 namespace {
diff --git a/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_impl.cc b/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_impl.cc
index a676152..0d51b5a6 100644
--- a/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_impl.cc
+++ b/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_impl.cc
@@ -361,13 +361,22 @@
 WorkerSchedulerImpl::CreateWebSchedulingTaskQueue(
     WebSchedulingQueueType queue_type,
     WebSchedulingPriority priority) {
+  // These queues are created from JavaScript, and JS should not be running
+  // after the worker is disposed.
+  CHECK(!is_disposed_);
+
   scoped_refptr<NonMainThreadTaskQueue> task_queue =
       thread_scheduler_->CreateTaskQueue(
           base::sequence_manager::QueueName::WORKER_WEB_SCHEDULING_TQ,
           NonMainThreadTaskQueue::QueueCreationParams()
               .SetWebSchedulingQueueType(queue_type)
               .SetWebSchedulingPriority(priority));
-  task_runners_.insert(task_queue, task_queue->CreateQueueEnabledVoter());
+  std::unique_ptr<base::sequence_manager::TaskQueue::QueueEnabledVoter> voter =
+      task_queue->CreateQueueEnabledVoter();
+  // TODO(crbug.com/528232589): Consider plumbing the initial enabled state to
+  // the QueueEnabledVoter constructor.
+  voter->SetVoteToEnable(paused_count_ == 0);
+  task_runners_.insert(task_queue, std::move(voter));
   return std::make_unique<NonMainThreadWebSchedulingTaskQueueImpl>(
       GetWeakPtr(), std::move(task_queue));
 }
diff --git a/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_impl_unittest.cc b/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_impl_unittest.cc
index 5b675db3..4b30cd1 100644
--- a/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_impl_unittest.cc
+++ b/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_impl_unittest.cc
@@ -692,7 +692,38 @@
   worker_scheduler_.reset();
 }
 
-TEST_F(WorkerSchedulerImplTest, WebSchedulerTaskQueueDestruction) {
+TEST_F(NonMainThreadWebSchedulingTaskQueueTest,
+       WebSchedulingTaskQueuesAreInitializedBasedOnPauseState) {
+  int counter = 0;
+
+  std::unique_ptr<WebSchedulingTaskQueue> queue =
+      worker_scheduler_->CreateWebSchedulingTaskQueue(
+          WebSchedulingQueueType::kTaskQueue,
+          WebSchedulingPriority::kUserVisiblePriority);
+  {
+    std::unique_ptr<WorkerScheduler::PauseHandle> pause_handle(
+        worker_scheduler_->Pause());
+
+    queue->GetTaskRunner()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
+    RunUntilIdle();
+    EXPECT_EQ(counter, 0);
+  }
+
+  RunUntilIdle();
+  EXPECT_EQ(counter, 1);
+
+  queue = worker_scheduler_->CreateWebSchedulingTaskQueue(
+      WebSchedulingQueueType::kTaskQueue,
+      WebSchedulingPriority::kUserVisiblePriority);
+  queue->GetTaskRunner()->PostTask(
+      FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
+  RunUntilIdle();
+  EXPECT_EQ(counter, 2);
+}
+
+TEST_F(WorkerSchedulerImplTest, WebSchedulingTaskQueueDestruction) {
   // This just makes sure that destroying queues before and after disposal
   // doesn't trigger any CHECKs or other issues.
   std::unique_ptr<WebSchedulingTaskQueue> queue1 =
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
index 5d751c96..808b6f2 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
@@ -895,6 +895,13 @@
   UnpausableTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
       FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
 
+  std::unique_ptr<WebSchedulingTaskQueue> web_scheduling_task_queue =
+      frame_scheduler_->CreateWebSchedulingTaskQueue(
+          WebSchedulingQueueType::kTaskQueue,
+          WebSchedulingPriority::kUserVisiblePriority);
+  web_scheduling_task_queue->GetTaskRunner()->PostTask(
+      FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
+
   frame_scheduler_->SetPaused(true);
 
   EXPECT_EQ(0, counter);
@@ -905,7 +912,30 @@
 
   EXPECT_EQ(1, counter);
   base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(5, counter);
+  EXPECT_EQ(6, counter);
+}
+
+TEST_F(FrameSchedulerImplTest, PauseBeforeWebSchedulingQueueCreated) {
+  int counter = 0;
+
+  frame_scheduler_->SetPaused(true);
+
+  std::unique_ptr<WebSchedulingTaskQueue> web_scheduling_task_queue =
+      frame_scheduler_->CreateWebSchedulingTaskQueue(
+          WebSchedulingQueueType::kTaskQueue,
+          WebSchedulingPriority::kUserVisiblePriority);
+  web_scheduling_task_queue->GetTaskRunner()->PostTask(
+      FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
+
+  EXPECT_EQ(0, counter);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(0, counter);
+
+  frame_scheduler_->SetPaused(false);
+
+  EXPECT_EQ(0, counter);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(1, counter);
 }
 
 namespace {
diff --git a/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_impl_unittest.cc b/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_impl_unittest.cc
index 5b675db3..4b30cd1 100644
--- a/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_impl_unittest.cc
+++ b/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_impl_unittest.cc
@@ -692,7 +692,38 @@
   worker_scheduler_.reset();
 }
 
-TEST_F(WorkerSchedulerImplTest, WebSchedulerTaskQueueDestruction) {
+TEST_F(NonMainThreadWebSchedulingTaskQueueTest,
+       WebSchedulingTaskQueuesAreInitializedBasedOnPauseState) {
+  int counter = 0;
+
+  std::unique_ptr<WebSchedulingTaskQueue> queue =
+      worker_scheduler_->CreateWebSchedulingTaskQueue(
+          WebSchedulingQueueType::kTaskQueue,
+          WebSchedulingPriority::kUserVisiblePriority);
+  {
+    std::unique_ptr<WorkerScheduler::PauseHandle> pause_handle(
+        worker_scheduler_->Pause());
+
+    queue->GetTaskRunner()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
+    RunUntilIdle();
+    EXPECT_EQ(counter, 0);
+  }
+
+  RunUntilIdle();
+  EXPECT_EQ(counter, 1);
+
+  queue = worker_scheduler_->CreateWebSchedulingTaskQueue(
+      WebSchedulingQueueType::kTaskQueue,
+      WebSchedulingPriority::kUserVisiblePriority);
+  queue->GetTaskRunner()->PostTask(
+      FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
+  RunUntilIdle();
+  EXPECT_EQ(counter, 2);
+}
+
+TEST_F(WorkerSchedulerImplTest, WebSchedulingTaskQueueDestruction) {
   // This just makes sure that destroying queues before and after disposal
   // doesn't trigger any CHECKs or other issues.
   std::unique_ptr<WebSchedulingTaskQueue> queue1 =
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.