blob: 05da47fca3f56ae0167ffeb0b34f2be0e901fa7f [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/test/bind.h"
#include "content/browser/devtools/worker_or_worklet_devtools_agent_host.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/test_browser_context.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
class TestAgentHost : public WorkerOrWorkletDevToolsAgentHost {
public:
TestAgentHost(
int process_id,
const GURL& url,
const std::string& name,
const base::UnguessableToken& devtools_worker_token,
const std::string& parent_id,
base::OnceCallback<void(DevToolsAgentHostImpl*)> destroyed_callback)
: WorkerOrWorkletDevToolsAgentHost(process_id,
url,
name,
devtools_worker_token,
parent_id,
std::move(destroyed_callback)) {
NotifyCreated();
}
using WorkerOrWorkletDevToolsAgentHost::Disconnected;
// DevToolsAgentHost implementation:
std::string GetType() override { return "test"; }
bool AttachSession(DevToolsSession* session) override { return true; }
void DetachSession(DevToolsSession* session) override {}
private:
~TestAgentHost() override = default;
};
} // namespace
class DevToolsRefcountTest : public testing::Test {
public:
DevToolsRefcountTest()
: task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP) {}
protected:
content::BrowserTaskEnvironment task_environment_;
TestBrowserContext browser_context_;
};
// Tests that WorkerOrWorkletDevToolsAgentHost doesn't underflow refcount
// when Disconnected() is called multiple times.
TEST_F(DevToolsRefcountTest, PreventDoubleRelease) {
MockRenderProcessHost renderer_host(&browser_context_);
const int process_id = renderer_host.GetDeprecatedID();
base::UnguessableToken token = base::UnguessableToken::Create();
bool destroyed_called = false;
auto destroyed_callback = base::BindLambdaForTesting(
[&](DevToolsAgentHostImpl* host) { destroyed_called = true; });
// 1. Create a TestAgentHost.
// It starts with refcount 1 (from our scoped_refptr) + 1 (from
// self-keep-alive in constructor).
scoped_refptr<TestAgentHost> agent_host = base::MakeRefCounted<TestAgentHost>(
process_id, GURL("http://example.com"), "worker", token, "parent_id",
std::move(destroyed_callback));
// 2. Call Disconnected() first time.
// This should trigger the first Release() and destroyed_callback.
agent_host->Disconnected();
EXPECT_TRUE(destroyed_called);
// 3. Call Disconnected() multiple times.
// If the fix is working, these subsequent calls won't cause double-release.
// We reset destroyed_called to ensure it's not called again.
destroyed_called = false;
agent_host->Disconnected();
agent_host->Disconnected();
EXPECT_FALSE(destroyed_called);
// 4. Verification: If we can still access agent_host, it hasn't been
// over-released. The refcount should be 1 (only our scoped_refptr).
EXPECT_TRUE(agent_host->HasOneRef());
}
} // namespace content