blob: 75991aeeb3310d2f79b6421d65fd10bc531418dd [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/performance_manager/worker_watcher.h"
#include <memory>
#include <utility>
#include <vector>
#include "base/callback_helpers.h"
#include "base/containers/contains.h"
#include "base/guid.h"
#include "base/macros.h"
#include "base/observer_list.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "components/performance_manager/frame_node_source.h"
#include "components/performance_manager/graph/frame_node_impl.h"
#include "components/performance_manager/graph/page_node_impl.h"
#include "components/performance_manager/graph/process_node_impl.h"
#include "components/performance_manager/graph/worker_node_impl.h"
#include "components/performance_manager/performance_manager_impl.h"
#include "components/performance_manager/process_node_source.h"
#include "components/performance_manager/public/features.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/shared_worker_service.h"
#include "content/public/test/fake_service_worker_context.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace performance_manager {
namespace {
// Generates a new sequential int ID. Used for things that need a unique ID.
int GenerateNextId() {
static int next_id = 0;
return next_id++;
}
// Generates a unique URL for a fake worker node.
GURL GenerateWorkerUrl() {
return GURL(base::StringPrintf("https://www.foo.org/worker_script_%d.js",
GenerateNextId()));
}
// Helper function to check that |worker_node| and |client_frame_node| are
// correctly hooked up together.
bool IsWorkerClient(WorkerNodeImpl* worker_node,
FrameNodeImpl* client_frame_node) {
return base::Contains(worker_node->client_frames(), client_frame_node) &&
base::Contains(client_frame_node->child_worker_nodes(), worker_node);
}
// Helper function to check that |worker_node| and |client_worker_node| are
// correctly hooked up together.
bool IsWorkerClient(WorkerNodeImpl* worker_node,
WorkerNodeImpl* client_worker_node) {
return base::Contains(worker_node->client_workers(), client_worker_node) &&
base::Contains(client_worker_node->child_workers(), worker_node);
}
// TestDedicatedWorkerService --------------------------------------------------
// A test DedicatedWorkerService that allows to simulate creating and destroying
// dedicated workers.
class TestDedicatedWorkerService : public content::DedicatedWorkerService {
public:
TestDedicatedWorkerService();
~TestDedicatedWorkerService() override;
// content::DedicatedWorkerService
void AddObserver(Observer* observer) override;
void RemoveObserver(Observer* observer) override;
void EnumerateDedicatedWorkers(Observer* observer) override;
// Creates a new dedicated worker and returns its ID.
const blink::DedicatedWorkerToken& CreateDedicatedWorker(
int worker_process_id,
content::GlobalFrameRoutingId client_render_frame_host_id);
// Destroys an existing dedicated worker.
void DestroyDedicatedWorker(const blink::DedicatedWorkerToken& token);
private:
base::ObserverList<Observer> observer_list_;
// Maps each running worker to its client RenderFrameHost ID.
base::flat_map<blink::DedicatedWorkerToken, content::GlobalFrameRoutingId>
dedicated_worker_client_frame_;
DISALLOW_COPY_AND_ASSIGN(TestDedicatedWorkerService);
};
TestDedicatedWorkerService::TestDedicatedWorkerService() = default;
TestDedicatedWorkerService::~TestDedicatedWorkerService() = default;
void TestDedicatedWorkerService::AddObserver(Observer* observer) {
observer_list_.AddObserver(observer);
}
void TestDedicatedWorkerService::RemoveObserver(Observer* observer) {
observer_list_.RemoveObserver(observer);
}
void TestDedicatedWorkerService::EnumerateDedicatedWorkers(Observer* observer) {
// Not implemented.
ADD_FAILURE();
}
const blink::DedicatedWorkerToken&
TestDedicatedWorkerService::CreateDedicatedWorker(
int worker_process_id,
content::GlobalFrameRoutingId client_render_frame_host_id) {
// Create a new token for the worker and add it to the map, along with its
// client ID.
const blink::DedicatedWorkerToken token;
auto result = dedicated_worker_client_frame_.emplace(
token, client_render_frame_host_id);
DCHECK(result.second); // Check inserted.
// Notify observers.
for (auto& observer : observer_list_) {
observer.OnWorkerCreated(token, worker_process_id,
client_render_frame_host_id);
}
return result.first->first;
}
void TestDedicatedWorkerService::DestroyDedicatedWorker(
const blink::DedicatedWorkerToken& token) {
auto it = dedicated_worker_client_frame_.find(token);
DCHECK(it != dedicated_worker_client_frame_.end());
// Notify observers that the worker is being destroyed.
for (auto& observer : observer_list_)
observer.OnBeforeWorkerDestroyed(token, it->second);
// Remove the worker ID from the map.
dedicated_worker_client_frame_.erase(it);
}
// TestSharedWorkerService -----------------------------------------------------
// A test SharedWorkerService that allows to simulate creating and destroying
// shared workers and adding clients to existing workers.
class TestSharedWorkerService : public content::SharedWorkerService {
public:
TestSharedWorkerService();
~TestSharedWorkerService() override;
// content::SharedWorkerService
void AddObserver(Observer* observer) override;
void RemoveObserver(Observer* observer) override;
void EnumerateSharedWorkers(Observer* observer) override;
bool TerminateWorker(const GURL& url,
const std::string& name,
const url::Origin& constructor_origin) override;
void Shutdown() override;
// Creates a new shared worker and returns its token.
blink::SharedWorkerToken CreateSharedWorker(int worker_process_id);
// Destroys a running shared worker.
void DestroySharedWorker(const blink::SharedWorkerToken& shared_worker_token);
// Adds a new frame client to an existing worker.
void AddClient(const blink::SharedWorkerToken& shared_worker_token,
content::GlobalFrameRoutingId client_render_frame_host_id);
// Removes an existing frame client from a worker.
void RemoveClient(const blink::SharedWorkerToken& shared_worker_token,
content::GlobalFrameRoutingId client_render_frame_host_id);
private:
base::ObserverList<Observer> observer_list_;
// Contains the set of clients for each running workers.
base::flat_map<blink::SharedWorkerToken,
base::flat_set<content::GlobalFrameRoutingId>>
shared_worker_client_frames_;
DISALLOW_COPY_AND_ASSIGN(TestSharedWorkerService);
};
TestSharedWorkerService::TestSharedWorkerService() = default;
TestSharedWorkerService::~TestSharedWorkerService() = default;
void TestSharedWorkerService::AddObserver(Observer* observer) {
observer_list_.AddObserver(observer);
}
void TestSharedWorkerService::RemoveObserver(Observer* observer) {
observer_list_.RemoveObserver(observer);
}
void TestSharedWorkerService::EnumerateSharedWorkers(Observer* observer) {
// Not implemented.
ADD_FAILURE();
}
bool TestSharedWorkerService::TerminateWorker(
const GURL& url,
const std::string& name,
const url::Origin& constructor_origin) {
// Not implemented.
ADD_FAILURE();
return false;
}
void TestSharedWorkerService::Shutdown() {
// Not implemented.
ADD_FAILURE();
}
blink::SharedWorkerToken TestSharedWorkerService::CreateSharedWorker(
int worker_process_id) {
// Create a new SharedWorkerToken for the worker and add it to the map.
const blink::SharedWorkerToken shared_worker_token;
bool inserted =
shared_worker_client_frames_.insert({shared_worker_token, {}}).second;
DCHECK(inserted);
// Notify observers.
for (auto& observer : observer_list_) {
observer.OnWorkerCreated(shared_worker_token, worker_process_id,
base::UnguessableToken::Create());
}
return shared_worker_token;
}
void TestSharedWorkerService::DestroySharedWorker(
const blink::SharedWorkerToken& shared_worker_token) {
auto it = shared_worker_client_frames_.find(shared_worker_token);
DCHECK(it != shared_worker_client_frames_.end());
// The worker should no longer have any clients.
DCHECK(it->second.empty());
// Notify observers that the worker is being destroyed.
for (auto& observer : observer_list_)
observer.OnBeforeWorkerDestroyed(shared_worker_token);
// Remove the worker ID from the map.
shared_worker_client_frames_.erase(it);
}
void TestSharedWorkerService::AddClient(
const blink::SharedWorkerToken& shared_worker_token,
content::GlobalFrameRoutingId client_render_frame_host_id) {
// Add the frame to the set of clients for this worker.
auto it = shared_worker_client_frames_.find(shared_worker_token);
DCHECK(it != shared_worker_client_frames_.end());
base::flat_set<content::GlobalFrameRoutingId>& client_frames = it->second;
bool inserted = client_frames.insert(client_render_frame_host_id).second;
DCHECK(inserted);
// Then notify observers.
for (auto& observer : observer_list_)
observer.OnClientAdded(shared_worker_token, client_render_frame_host_id);
}
void TestSharedWorkerService::RemoveClient(
const blink::SharedWorkerToken& shared_worker_token,
content::GlobalFrameRoutingId client_render_frame_host_id) {
// Notify observers.
for (auto& observer : observer_list_)
observer.OnClientRemoved(shared_worker_token, client_render_frame_host_id);
// Then remove the frame from the set of clients of this worker.
auto it = shared_worker_client_frames_.find(shared_worker_token);
DCHECK(it != shared_worker_client_frames_.end());
base::flat_set<content::GlobalFrameRoutingId>& client_frames = it->second;
size_t removed = client_frames.erase(client_render_frame_host_id);
DCHECK_EQ(removed, 1u);
}
// TestServiceWorkerContext ----------------------------------------------------
// A test ServiceWorkerContext that allows to simulate a worker starting and
// stopping and adding clients to running workers.
//
// Extends content::FakeServiceWorkerContext to avoid reimplementing all the
// unused virtual functions.
class TestServiceWorkerContext : public content::FakeServiceWorkerContext {
public:
TestServiceWorkerContext();
~TestServiceWorkerContext() override;
TestServiceWorkerContext(const TestServiceWorkerContext&) = delete;
TestServiceWorkerContext& operator=(const TestServiceWorkerContext&) = delete;
// content::FakeServiceWorkerContext:
void AddObserver(content::ServiceWorkerContextObserver* observer) override;
void RemoveObserver(content::ServiceWorkerContextObserver* observer) override;
// Creates a new service worker and returns its version ID.
int64_t CreateServiceWorker();
// Deletes an existing service worker.
void DestroyServiceWorker(int64_t version_id);
// Starts an existing service worker.
void StartServiceWorker(int64_t version_id, int worker_process_id);
// Stops a service shared worker.
void StopServiceWorker(int64_t version_id);
// Adds a new client to an existing service worker and returns its generated
// client UUID.
std::string AddClient(int64_t version_id,
const content::ServiceWorkerClientInfo& client_info);
// Adds a new client to an existing service worker with the provided
// client UUID. Returns |client_uuid| for convenience.
std::string AddClientWithClientID(
int64_t version_id,
std::string client_uuid,
const content::ServiceWorkerClientInfo& client_info);
// Removes an existing client from a worker.
void RemoveClient(int64_t version_id, const std::string& client_uuid);
// Simulates when the navigation commits, meaning that the RenderFrameHost is
// now available for a window client. Not valid for worker clients.
void OnControlleeNavigationCommitted(
int64_t version_id,
const std::string& client_uuid,
content::GlobalFrameRoutingId render_frame_host_id);
private:
base::ObserverList<content::ServiceWorkerContextObserver>::Unchecked
observer_list_;
// The ID that the next service worker will be assigned.
int64_t next_service_worker_instance_id_ = 0;
struct ServiceWorkerInfo {
bool is_running = false;
// Contains all the clients
base::flat_set<std::string /*client_uuid*/> clients;
};
base::flat_map<int64_t /*version_id*/, ServiceWorkerInfo>
service_worker_infos_;
};
TestServiceWorkerContext::TestServiceWorkerContext() = default;
TestServiceWorkerContext::~TestServiceWorkerContext() = default;
void TestServiceWorkerContext::AddObserver(
content::ServiceWorkerContextObserver* observer) {
observer_list_.AddObserver(observer);
}
void TestServiceWorkerContext::RemoveObserver(
content::ServiceWorkerContextObserver* observer) {
observer_list_.RemoveObserver(observer);
}
int64_t TestServiceWorkerContext::CreateServiceWorker() {
// Create a new version ID and add it to the map.
int64_t version_id = next_service_worker_instance_id_++;
bool inserted = service_worker_infos_.insert({version_id, {}}).second;
DCHECK(inserted);
return version_id;
}
void TestServiceWorkerContext::DestroyServiceWorker(int64_t version_id) {
auto it = service_worker_infos_.find(version_id);
DCHECK(it != service_worker_infos_.end());
const ServiceWorkerInfo& info = it->second;
// Can only delete a service worker that isn't running and has no clients.
DCHECK(!info.is_running);
DCHECK(info.clients.empty());
// Remove the worker instance from the map.
service_worker_infos_.erase(it);
}
void TestServiceWorkerContext::StartServiceWorker(int64_t version_id,
int worker_process_id) {
auto it = service_worker_infos_.find(version_id);
DCHECK(it != service_worker_infos_.end());
ServiceWorkerInfo& info = it->second;
DCHECK(!info.is_running);
info.is_running = true;
// Notify observers.
GURL worker_url = GenerateWorkerUrl();
GURL scope_url;
for (auto& observer : observer_list_) {
observer.OnVersionStartedRunning(
version_id, content::ServiceWorkerRunningInfo(
worker_url, scope_url, worker_process_id,
blink::ServiceWorkerToken()));
}
}
void TestServiceWorkerContext::StopServiceWorker(int64_t version_id) {
auto it = service_worker_infos_.find(version_id);
DCHECK(it != service_worker_infos_.end());
ServiceWorkerInfo& info = it->second;
DCHECK(info.is_running);
info.is_running = false;
// Notify observers that the worker is terminating.
for (auto& observer : observer_list_)
observer.OnVersionStoppedRunning(version_id);
}
std::string TestServiceWorkerContext::AddClient(
int64_t version_id,
const content::ServiceWorkerClientInfo& client_info) {
return AddClientWithClientID(version_id, base::GenerateGUID(), client_info);
}
std::string TestServiceWorkerContext::AddClientWithClientID(
int64_t version_id,
std::string client_uuid,
const content::ServiceWorkerClientInfo& client_info) {
auto it = service_worker_infos_.find(version_id);
DCHECK(it != service_worker_infos_.end());
ServiceWorkerInfo& info = it->second;
bool inserted = info.clients.insert(client_uuid).second;
DCHECK(inserted);
for (auto& observer : observer_list_)
observer.OnControlleeAdded(version_id, client_uuid, client_info);
return client_uuid;
}
void TestServiceWorkerContext::RemoveClient(int64_t version_id,
const std::string& client_uuid) {
auto it = service_worker_infos_.find(version_id);
DCHECK(it != service_worker_infos_.end());
ServiceWorkerInfo& info = it->second;
size_t removed = info.clients.erase(client_uuid);
DCHECK_EQ(removed, 1u);
for (auto& observer : observer_list_)
observer.OnControlleeRemoved(version_id, client_uuid);
}
void TestServiceWorkerContext::OnControlleeNavigationCommitted(
int64_t version_id,
const std::string& client_uuid,
content::GlobalFrameRoutingId render_frame_host_id) {
auto it = service_worker_infos_.find(version_id);
DCHECK(it != service_worker_infos_.end());
ServiceWorkerInfo& info = it->second;
DCHECK(base::Contains(info.clients, client_uuid));
for (auto& observer : observer_list_) {
observer.OnControlleeNavigationCommitted(version_id, client_uuid,
render_frame_host_id);
}
}
// TestProcessNodeSource -------------------------------------------------------
// A test ProcessNodeSource that allows creating process nodes on demand to
// "host" frames and workers.
class TestProcessNodeSource : public ProcessNodeSource {
public:
TestProcessNodeSource();
~TestProcessNodeSource() override;
// ProcessNodeSource:
ProcessNodeImpl* GetProcessNode(int render_process_id) override;
// Creates a process node and returns its generated render process ID.
int CreateProcessNode();
private:
// Maps render process IDs with their associated process node.
base::flat_map<int, std::unique_ptr<ProcessNodeImpl>> process_node_map_;
DISALLOW_COPY_AND_ASSIGN(TestProcessNodeSource);
};
TestProcessNodeSource::TestProcessNodeSource() = default;
TestProcessNodeSource::~TestProcessNodeSource() {
std::vector<std::unique_ptr<NodeBase>> nodes;
nodes.reserve(process_node_map_.size());
for (auto& kv : process_node_map_) {
std::unique_ptr<ProcessNodeImpl> process_node = std::move(kv.second);
nodes.push_back(std::move(process_node));
}
PerformanceManagerImpl::BatchDeleteNodes(std::move(nodes));
process_node_map_.clear();
}
ProcessNodeImpl* TestProcessNodeSource::GetProcessNode(int render_process_id) {
auto it = process_node_map_.find(render_process_id);
DCHECK(it != process_node_map_.end());
return it->second.get();
}
int TestProcessNodeSource::CreateProcessNode() {
// Generate a render process ID for this process node.
int render_process_id = GenerateNextId();
// Create the process node and insert it into the map.
auto process_node = PerformanceManagerImpl::CreateProcessNode(
content::PROCESS_TYPE_RENDERER, RenderProcessHostProxy());
bool inserted =
process_node_map_.insert({render_process_id, std::move(process_node)})
.second;
DCHECK(inserted);
return render_process_id;
}
// TestFrameNodeSource ---------------------------------------------------------
class TestFrameNodeSource : public FrameNodeSource {
public:
TestFrameNodeSource();
~TestFrameNodeSource() override;
// FrameNodeSource:
FrameNodeImpl* GetFrameNode(
content::GlobalFrameRoutingId render_frame_host_id) override;
void SubscribeToFrameNode(content::GlobalFrameRoutingId render_frame_host_id,
OnbeforeFrameNodeRemovedCallback
on_before_frame_node_removed_callback) override;
void UnsubscribeFromFrameNode(
content::GlobalFrameRoutingId render_frame_host_id) override;
// Creates a frame node and returns its generated render frame host id.
content::GlobalFrameRoutingId CreateFrameNode(int render_process_id,
ProcessNodeImpl* process_node);
// Deletes an existing frame node and notify subscribers.
void DeleteFrameNode(content::GlobalFrameRoutingId render_frame_host_id);
private:
// Helper function that invokes the OnBeforeFrameNodeRemovedCallback
// associated with |frame_node| and removes it from the map.
void InvokeAndRemoveCallback(FrameNodeImpl* frame_node);
// The page node that hosts all frames.
std::unique_ptr<PageNodeImpl> page_node_;
// Maps each frame's render frame host id with their associated frame node.
base::flat_map<content::GlobalFrameRoutingId, std::unique_ptr<FrameNodeImpl>>
frame_node_map_;
// Maps each observed frame node to their callback.
base::flat_map<FrameNodeImpl*, OnbeforeFrameNodeRemovedCallback>
frame_node_callbacks_;
DISALLOW_COPY_AND_ASSIGN(TestFrameNodeSource);
};
TestFrameNodeSource::TestFrameNodeSource()
: page_node_(
PerformanceManagerImpl::CreatePageNode(WebContentsProxy(),
"page_node_context_id",
GURL(),
false,
false,
base::TimeTicks::Now())) {}
TestFrameNodeSource::~TestFrameNodeSource() {
std::vector<std::unique_ptr<NodeBase>> nodes;
nodes.push_back(std::move(page_node_));
nodes.reserve(frame_node_map_.size());
for (auto& kv : frame_node_map_)
nodes.push_back(std::move(kv.second));
PerformanceManagerImpl::BatchDeleteNodes(std::move(nodes));
frame_node_map_.clear();
}
FrameNodeImpl* TestFrameNodeSource::GetFrameNode(
content::GlobalFrameRoutingId render_frame_host_id) {
auto it = frame_node_map_.find(render_frame_host_id);
return it != frame_node_map_.end() ? it->second.get() : nullptr;
}
void TestFrameNodeSource::SubscribeToFrameNode(
content::GlobalFrameRoutingId render_frame_host_id,
OnbeforeFrameNodeRemovedCallback on_before_frame_node_removed_callback) {
FrameNodeImpl* frame_node = GetFrameNode(render_frame_host_id);
DCHECK(frame_node);
bool inserted =
frame_node_callbacks_
.emplace(frame_node, std::move(on_before_frame_node_removed_callback))
.second;
DCHECK(inserted);
}
void TestFrameNodeSource::UnsubscribeFromFrameNode(
content::GlobalFrameRoutingId render_frame_host_id) {
FrameNodeImpl* frame_node = GetFrameNode(render_frame_host_id);
DCHECK(frame_node);
size_t removed = frame_node_callbacks_.erase(frame_node);
DCHECK_EQ(removed, 1u);
}
content::GlobalFrameRoutingId TestFrameNodeSource::CreateFrameNode(
int render_process_id,
ProcessNodeImpl* process_node) {
int frame_id = GenerateNextId();
content::GlobalFrameRoutingId render_frame_host_id(render_process_id,
frame_id);
auto frame_node = PerformanceManagerImpl::CreateFrameNode(
process_node, page_node_.get(), nullptr, 0, frame_id,
blink::LocalFrameToken(), 0, 0);
bool inserted =
frame_node_map_.insert({render_frame_host_id, std::move(frame_node)})
.second;
DCHECK(inserted);
return render_frame_host_id;
}
void TestFrameNodeSource::DeleteFrameNode(
content::GlobalFrameRoutingId render_frame_host_id) {
auto it = frame_node_map_.find(render_frame_host_id);
DCHECK(it != frame_node_map_.end());
FrameNodeImpl* frame_node = it->second.get();
// Notify the subscriber then delete the node.
InvokeAndRemoveCallback(frame_node);
PerformanceManagerImpl::DeleteNode(std::move(it->second));
frame_node_map_.erase(it);
}
void TestFrameNodeSource::InvokeAndRemoveCallback(FrameNodeImpl* frame_node) {
auto it = frame_node_callbacks_.find(frame_node);
DCHECK(it != frame_node_callbacks_.end());
std::move(it->second).Run(frame_node);
frame_node_callbacks_.erase(it);
}
} // namespace
class WorkerWatcherTest : public testing::Test {
public:
WorkerWatcherTest();
~WorkerWatcherTest() override;
// testing::Test:
void SetUp() override;
void TearDown() override;
// Wraps a |graph_callback| and ensures the task completes before returning.
void CallOnGraphAndWait(
PerformanceManagerImpl::GraphImplCallback graph_callback);
// Retrieves an existing worker node.
WorkerNodeImpl* GetDedicatedWorkerNode(
const blink::DedicatedWorkerToken& token);
WorkerNodeImpl* GetSharedWorkerNode(
const blink::SharedWorkerToken& shared_worker_token);
WorkerNodeImpl* GetServiceWorkerNode(int64_t version_id);
TestDedicatedWorkerService* dedicated_worker_service() {
return &dedicated_worker_service_;
}
TestSharedWorkerService* shared_worker_service() {
return &shared_worker_service_;
}
TestServiceWorkerContext* service_worker_context() {
return &service_worker_context_;
}
TestProcessNodeSource* process_node_source() {
return process_node_source_.get();
}
TestFrameNodeSource* frame_node_source() { return frame_node_source_.get(); }
protected:
// Test the frame destroyed case with or without service worker relationship
void TestFrameDestroyed(bool enable_service_worker_relationships);
private:
base::test::TaskEnvironment task_environment_;
TestDedicatedWorkerService dedicated_worker_service_;
TestSharedWorkerService shared_worker_service_;
TestServiceWorkerContext service_worker_context_;
std::unique_ptr<PerformanceManagerImpl> performance_manager_;
std::unique_ptr<TestProcessNodeSource> process_node_source_;
std::unique_ptr<TestFrameNodeSource> frame_node_source_;
// The WorkerWatcher that's being tested.
std::unique_ptr<WorkerWatcher> worker_watcher_;
DISALLOW_COPY_AND_ASSIGN(WorkerWatcherTest);
};
WorkerWatcherTest::WorkerWatcherTest() = default;
WorkerWatcherTest::~WorkerWatcherTest() = default;
void WorkerWatcherTest::SetUp() {
performance_manager_ = PerformanceManagerImpl::Create(base::DoNothing());
process_node_source_ = std::make_unique<TestProcessNodeSource>();
frame_node_source_ = std::make_unique<TestFrameNodeSource>();
worker_watcher_ = std::make_unique<WorkerWatcher>(
"browser_context_id", &dedicated_worker_service_, &shared_worker_service_,
&service_worker_context_, process_node_source_.get(),
frame_node_source_.get());
}
void WorkerWatcherTest::TearDown() {
// Clean up the performance manager correctly.
worker_watcher_->TearDown();
worker_watcher_ = nullptr;
// Delete the TestFrameNodeSource and the TestProcessNodeSource in
// that order since they own graph nodes.
frame_node_source_ = nullptr;
process_node_source_ = nullptr;
PerformanceManagerImpl::Destroy(std::move(performance_manager_));
}
void WorkerWatcherTest::CallOnGraphAndWait(
PerformanceManagerImpl::GraphImplCallback graph_callback) {
base::RunLoop run_loop;
PerformanceManagerImpl::CallOnGraphImpl(
FROM_HERE,
base::BindLambdaForTesting(
[graph_callback = std::move(graph_callback),
quit_closure = run_loop.QuitClosure()](GraphImpl* graph) mutable {
std::move(graph_callback).Run(graph);
quit_closure.Run();
}));
run_loop.Run();
}
WorkerNodeImpl* WorkerWatcherTest::GetDedicatedWorkerNode(
const blink::DedicatedWorkerToken& token) {
return worker_watcher_->GetDedicatedWorkerNode(token);
}
WorkerNodeImpl* WorkerWatcherTest::GetSharedWorkerNode(
const blink::SharedWorkerToken& shared_worker_token) {
return worker_watcher_->GetSharedWorkerNode(shared_worker_token);
}
WorkerNodeImpl* WorkerWatcherTest::GetServiceWorkerNode(int64_t version_id) {
return worker_watcher_->GetServiceWorkerNode(version_id);
}
// This test creates one dedicated worker.
TEST_F(WorkerWatcherTest, SimpleDedicatedWorker) {
int render_process_id = process_node_source()->CreateProcessNode();
// Create the frame node.
content::GlobalFrameRoutingId render_frame_host_id =
frame_node_source()->CreateFrameNode(
render_process_id,
process_node_source()->GetProcessNode(render_process_id));
// Create the worker.
const blink::DedicatedWorkerToken token =
dedicated_worker_service()->CreateDedicatedWorker(render_process_id,
render_frame_host_id);
// Check expectations on the graph.
CallOnGraphAndWait(base::BindLambdaForTesting(
[process_node = process_node_source()->GetProcessNode(render_process_id),
worker_node = GetDedicatedWorkerNode(token),
client_frame_node = frame_node_source()->GetFrameNode(
render_frame_host_id)](GraphImpl* graph) {
EXPECT_TRUE(graph->NodeInGraph(worker_node));
EXPECT_EQ(worker_node->worker_type(),
WorkerNode::WorkerType::kDedicated);
EXPECT_EQ(worker_node->process_node(), process_node);
EXPECT_TRUE(IsWorkerClient(worker_node, client_frame_node));
}));
// Disconnect and clean up the dedicated worker.
dedicated_worker_service()->DestroyDedicatedWorker(token);
}
// This test creates one shared worker with one client frame.
TEST_F(WorkerWatcherTest, SimpleSharedWorker) {
int render_process_id = process_node_source()->CreateProcessNode();
// Create the frame node.
content::GlobalFrameRoutingId render_frame_host_id =
frame_node_source()->CreateFrameNode(
render_process_id,
process_node_source()->GetProcessNode(render_process_id));
// Create the worker.
const blink::SharedWorkerToken shared_worker_token =
shared_worker_service()->CreateSharedWorker(render_process_id);
// Connect the frame to the worker.
shared_worker_service()->AddClient(shared_worker_token, render_frame_host_id);
// Check expectations on the graph.
CallOnGraphAndWait(base::BindLambdaForTesting(
[process_node = process_node_source()->GetProcessNode(render_process_id),
worker_node = GetSharedWorkerNode(shared_worker_token),
client_frame_node = frame_node_source()->GetFrameNode(
render_frame_host_id)](GraphImpl* graph) {
EXPECT_TRUE(graph->NodeInGraph(worker_node));
EXPECT_EQ(worker_node->worker_type(), WorkerNode::WorkerType::kShared);
EXPECT_EQ(worker_node->process_node(), process_node);
EXPECT_TRUE(IsWorkerClient(worker_node, client_frame_node));
}));
// Disconnect and clean up the shared worker.
shared_worker_service()->RemoveClient(shared_worker_token,
render_frame_host_id);
shared_worker_service()->DestroySharedWorker(shared_worker_token);
}
// This test creates one service worker with one client frame.
TEST_F(WorkerWatcherTest, ServiceWorkerFrameClient) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(
features::kServiceWorkerRelationshipsInGraph);
int render_process_id = process_node_source()->CreateProcessNode();
// Create and start the service worker.
int64_t service_worker_version_id =
service_worker_context()->CreateServiceWorker();
service_worker_context()->StartServiceWorker(service_worker_version_id,
render_process_id);
// Add a frame tree node as a client of the service worker.
int frame_tree_node_id = GenerateNextId();
std::string service_worker_client_uuid = service_worker_context()->AddClient(
service_worker_version_id,
content::ServiceWorkerClientInfo(frame_tree_node_id));
// Check expectations on the graph.
CallOnGraphAndWait(base::BindLambdaForTesting(
[process_node = process_node_source()->GetProcessNode(render_process_id),
worker_node =
GetServiceWorkerNode(service_worker_version_id)](GraphImpl* graph) {
EXPECT_TRUE(graph->NodeInGraph(worker_node));
EXPECT_EQ(worker_node->worker_type(), WorkerNode::WorkerType::kService);
EXPECT_EQ(worker_node->process_node(), process_node);
// The frame can not be connected to the service worker until its
// render frame host is available, which happens when the navigation
// commits.
EXPECT_TRUE(worker_node->client_frames().empty());
}));
// Now simulate the navigation commit.
content::GlobalFrameRoutingId render_frame_host_id =
frame_node_source()->CreateFrameNode(
render_process_id,
process_node_source()->GetProcessNode(render_process_id));
service_worker_context()->OnControlleeNavigationCommitted(
service_worker_version_id, service_worker_client_uuid,
render_frame_host_id);
// Check expectations on the graph.
CallOnGraphAndWait(base::BindLambdaForTesting(
[process_node = process_node_source()->GetProcessNode(render_process_id),
worker_node = GetServiceWorkerNode(service_worker_version_id),
client_frame_node = frame_node_source()->GetFrameNode(
render_frame_host_id)](GraphImpl* graph) {
EXPECT_TRUE(graph->NodeInGraph(worker_node));
EXPECT_EQ(worker_node->worker_type(), WorkerNode::WorkerType::kService);
EXPECT_EQ(worker_node->process_node(), process_node);
EXPECT_TRUE(IsWorkerClient(worker_node, client_frame_node));
}));
// Disconnect and clean up the service worker.
service_worker_context()->RemoveClient(service_worker_version_id,
service_worker_client_uuid);
service_worker_context()->StopServiceWorker(service_worker_version_id);
service_worker_context()->DestroyServiceWorker(service_worker_version_id);
}
// Ensures that the WorkerWatcher handles the case where a frame with a service
// worker is (briefly?) an uncommitted client of two versions. This presumably
// happens on version update or some such, or perhaps when a frame is a
// bona-fide client of two service workers. Apparently this happens quite
// rarely in the field.
TEST_F(WorkerWatcherTest, ServiceWorkerFrameClientOfTwoWorkers) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(
features::kServiceWorkerRelationshipsInGraph);
int render_process_id = process_node_source()->CreateProcessNode();
// Create and start both service workers.
int64_t first_service_worker_version_id =
service_worker_context()->CreateServiceWorker();
service_worker_context()->StartServiceWorker(first_service_worker_version_id,
render_process_id);
int64_t second_service_worker_version_id =
service_worker_context()->CreateServiceWorker();
service_worker_context()->StartServiceWorker(second_service_worker_version_id,
render_process_id);
// Add a frame tree node as a client of both service workers.
int frame_tree_node_id = GenerateNextId();
std::string service_worker_client_uuid = service_worker_context()->AddClient(
first_service_worker_version_id,
content::ServiceWorkerClientInfo(frame_tree_node_id));
service_worker_context()->AddClientWithClientID(
second_service_worker_version_id, service_worker_client_uuid,
content::ServiceWorkerClientInfo(frame_tree_node_id));
// Check expectations on the graph.
CallOnGraphAndWait(base::BindLambdaForTesting(
[process_node = process_node_source()->GetProcessNode(render_process_id),
first_worker_node =
GetServiceWorkerNode(first_service_worker_version_id),
second_worker_node = GetServiceWorkerNode(
second_service_worker_version_id)](GraphImpl* graph) {
EXPECT_TRUE(graph->NodeInGraph(first_worker_node));
EXPECT_EQ(first_worker_node->worker_type(),
WorkerNode::WorkerType::kService);
EXPECT_EQ(first_worker_node->process_node(), process_node);
// The frame was never added as a client of the service worker.
EXPECT_TRUE(first_worker_node->client_frames().empty());
EXPECT_TRUE(graph->NodeInGraph(second_worker_node));
EXPECT_EQ(second_worker_node->worker_type(),
WorkerNode::WorkerType::kService);
EXPECT_EQ(second_worker_node->process_node(), process_node);
// The frame was never added as a client of the service worker.
EXPECT_TRUE(second_worker_node->client_frames().empty());
}));
// Disconnect and clean up the service worker.
service_worker_context()->RemoveClient(first_service_worker_version_id,
service_worker_client_uuid);
service_worker_context()->StopServiceWorker(first_service_worker_version_id);
service_worker_context()->DestroyServiceWorker(
first_service_worker_version_id);
service_worker_context()->RemoveClient(second_service_worker_version_id,
service_worker_client_uuid);
service_worker_context()->StopServiceWorker(second_service_worker_version_id);
service_worker_context()->DestroyServiceWorker(
second_service_worker_version_id);
}
// Ensures that the WorkerWatcher handles the case where a frame with a service
// worker has a double client relationship with a service worker.
// This appears to be happening out in the real world, if quite rarely.
// See https://crbug.com/1143281#c33.
TEST_F(WorkerWatcherTest, ServiceWorkerTwoFrameClientRelationships) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(
features::kServiceWorkerRelationshipsInGraph);
int render_process_id = process_node_source()->CreateProcessNode();
// Create and start a service worker.
int64_t service_worker_version_id =
service_worker_context()->CreateServiceWorker();
service_worker_context()->StartServiceWorker(service_worker_version_id,
render_process_id);
// Add a frame tree node as a client of a service worker.
int frame_tree_node_id = GenerateNextId();
std::string first_client_uuid = service_worker_context()->AddClient(
service_worker_version_id,
content::ServiceWorkerClientInfo(frame_tree_node_id));
// Check expectations on the graph.
CallOnGraphAndWait(base::BindLambdaForTesting(
[process_node = process_node_source()->GetProcessNode(render_process_id),
worker_node =
GetServiceWorkerNode(service_worker_version_id)](GraphImpl* graph) {
EXPECT_TRUE(graph->NodeInGraph(worker_node));
EXPECT_EQ(worker_node->worker_type(), WorkerNode::WorkerType::kService);
// The frame was not yet added as a client.
EXPECT_TRUE(worker_node->client_frames().empty());
}));
// Add a second client relationship between the same two entities.
std::string second_client_uuid = service_worker_context()->AddClient(
service_worker_version_id,
content::ServiceWorkerClientInfo(frame_tree_node_id));
// Now simulate the navigation commit.
content::GlobalFrameRoutingId render_frame_host_id =
frame_node_source()->CreateFrameNode(
render_process_id,
process_node_source()->GetProcessNode(render_process_id));
service_worker_context()->OnControlleeNavigationCommitted(
service_worker_version_id, first_client_uuid, render_frame_host_id);
CallOnGraphAndWait(base::BindLambdaForTesting(
[process_node = process_node_source()->GetProcessNode(render_process_id),
service_worker_node = GetServiceWorkerNode(service_worker_version_id),
client_frame_node = frame_node_source()->GetFrameNode(
render_frame_host_id)](GraphImpl* graph) {
EXPECT_TRUE(graph->NodeInGraph(service_worker_node));
EXPECT_EQ(service_worker_node->worker_type(),
WorkerNode::WorkerType::kService);
EXPECT_EQ(1u, service_worker_node->client_frames().size());
EXPECT_TRUE(IsWorkerClient(service_worker_node, client_frame_node));
}));
// Commit the second controllee navigation.
service_worker_context()->OnControlleeNavigationCommitted(
service_worker_version_id, second_client_uuid, render_frame_host_id);
// Verify that the graph is still the same.
CallOnGraphAndWait(base::BindLambdaForTesting(
[process_node = process_node_source()->GetProcessNode(render_process_id),
service_worker_node = GetServiceWorkerNode(service_worker_version_id),
client_frame_node = frame_node_source()->GetFrameNode(
render_frame_host_id)](GraphImpl* graph) {
EXPECT_TRUE(graph->NodeInGraph(service_worker_node));
EXPECT_EQ(service_worker_node->worker_type(),
WorkerNode::WorkerType::kService);
EXPECT_EQ(1u, service_worker_node->client_frames().size());
EXPECT_TRUE(IsWorkerClient(service_worker_node, client_frame_node));
}));
// Remove the first client relationship.
service_worker_context()->RemoveClient(service_worker_version_id,
first_client_uuid);
// Verify that the graph is still the same.
CallOnGraphAndWait(base::BindLambdaForTesting(
[process_node = process_node_source()->GetProcessNode(render_process_id),
service_worker_node = GetServiceWorkerNode(service_worker_version_id),
client_frame_node = frame_node_source()->GetFrameNode(
render_frame_host_id)](GraphImpl* graph) {
EXPECT_TRUE(graph->NodeInGraph(service_worker_node));
EXPECT_EQ(service_worker_node->worker_type(),
WorkerNode::WorkerType::kService);
EXPECT_EQ(1u, service_worker_node->client_frames().size());
EXPECT_TRUE(IsWorkerClient(service_worker_node, client_frame_node));
}));
// Teardown.
service_worker_context()->RemoveClient(service_worker_version_id,
second_client_uuid);
service_worker_context()->StopServiceWorker(service_worker_version_id);
service_worker_context()->DestroyServiceWorker(service_worker_version_id);
}
// Ensures that the WorkerWatcher handles the case where a frame with a service
// worker is created but it's navigation is never committed before the
// FrameTreeNode is destroyed.
TEST_F(WorkerWatcherTest, ServiceWorkerFrameClientDestroyedBeforeCommit) {
int render_process_id = process_node_source()->CreateProcessNode();
// Create and start the service worker.
int64_t service_worker_version_id =
service_worker_context()->CreateServiceWorker();
service_worker_context()->StartServiceWorker(service_worker_version_id,
render_process_id);
// Add a frame tree node as a client of the service worker.
int frame_tree_node_id = GenerateNextId();
std::string service_worker_client_uuid = service_worker_context()->AddClient(
service_worker_version_id,
content::ServiceWorkerClientInfo(frame_tree_node_id));
// Check expectations on the graph.
CallOnGraphAndWait(base::BindLambdaForTesting(
[process_node = process_node_source()->GetProcessNode(render_process_id),
worker_node =
GetServiceWorkerNode(service_worker_version_id)](GraphImpl* graph) {
EXPECT_TRUE(graph->NodeInGraph(worker_node));
EXPECT_EQ(worker_node->worker_type(), WorkerNode::WorkerType::kService);
EXPECT_EQ(worker_node->process_node(), process_node);
// The frame was never added as a client of the service worker.
EXPECT_TRUE(worker_node->client_frames().empty());
}));
// Disconnect and clean up the service worker.
service_worker_context()->RemoveClient(service_worker_version_id,
service_worker_client_uuid);
service_worker_context()->StopServiceWorker(service_worker_version_id);
service_worker_context()->DestroyServiceWorker(service_worker_version_id);
}
TEST_F(WorkerWatcherTest, AllTypesOfServiceWorkerClients) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(
features::kServiceWorkerRelationshipsInGraph);
int render_process_id = process_node_source()->CreateProcessNode();
// Create and start the service worker.
int64_t service_worker_version_id =
service_worker_context()->CreateServiceWorker();
service_worker_context()->StartServiceWorker(service_worker_version_id,
render_process_id);
// Create a client of each type and connect them to the service worker.
// Frame client.
int frame_tree_node_id = GenerateNextId();
std::string frame_client_uuid = service_worker_context()->AddClient(
service_worker_version_id,
content::ServiceWorkerClientInfo(frame_tree_node_id));
content::GlobalFrameRoutingId render_frame_host_id =
frame_node_source()->CreateFrameNode(
render_process_id,
process_node_source()->GetProcessNode(render_process_id));
service_worker_context()->OnControlleeNavigationCommitted(
service_worker_version_id, frame_client_uuid, render_frame_host_id);
// Dedicated worker client.
blink::DedicatedWorkerToken dedicated_worker_token =
dedicated_worker_service()->CreateDedicatedWorker(render_process_id,
render_frame_host_id);
std::string dedicated_worker_client_uuid =
service_worker_context()->AddClient(
service_worker_version_id,
content::ServiceWorkerClientInfo(dedicated_worker_token));
// Shared worker client.
blink::SharedWorkerToken shared_worker_token =
shared_worker_service()->CreateSharedWorker(render_process_id);
std::string shared_worker_client_uuid = service_worker_context()->AddClient(
service_worker_version_id,
content::ServiceWorkerClientInfo(shared_worker_token));
// Check expectations on the graph.
CallOnGraphAndWait(base::BindLambdaForTesting(
[process_node = process_node_source()->GetProcessNode(render_process_id),
service_worker_node = GetServiceWorkerNode(service_worker_version_id),
client_frame_node =
frame_node_source()->GetFrameNode(render_frame_host_id),
dedicated_worker_node = GetDedicatedWorkerNode(dedicated_worker_token),
shared_worker_node =
GetSharedWorkerNode(shared_worker_token)](GraphImpl* graph) {
EXPECT_TRUE(graph->NodeInGraph(service_worker_node));
EXPECT_TRUE(IsWorkerClient(service_worker_node, client_frame_node));
EXPECT_TRUE(IsWorkerClient(service_worker_node, dedicated_worker_node));
EXPECT_TRUE(IsWorkerClient(service_worker_node, shared_worker_node));
}));
// Disconnect and clean up the service worker and its clients.
service_worker_context()->RemoveClient(service_worker_version_id,
shared_worker_client_uuid);
shared_worker_service()->DestroySharedWorker(shared_worker_token);
service_worker_context()->RemoveClient(service_worker_version_id,
dedicated_worker_client_uuid);
dedicated_worker_service()->DestroyDedicatedWorker(dedicated_worker_token);
service_worker_context()->RemoveClient(service_worker_version_id,
frame_client_uuid);
service_worker_context()->StopServiceWorker(service_worker_version_id);
service_worker_context()->DestroyServiceWorker(service_worker_version_id);
}
// Tests that the WorkerWatcher can handle the case where the service worker
// starts after it has been assigned a client. In this case, the clients are not
// connected to the service worker until it starts. It also tests that when the
// service worker stops, its existing clients are also disconnected.
TEST_F(WorkerWatcherTest, ServiceWorkerStartsAndStopsWithExistingClients) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(
features::kServiceWorkerRelationshipsInGraph);
int render_process_id = process_node_source()->CreateProcessNode();
// Create the worker.
int64_t service_worker_version_id =
service_worker_context()->CreateServiceWorker();
// Create a client of each type and connect them to the service worker.
// Frame client.
int frame_tree_node_id = GenerateNextId();
std::string frame_client_uuid = service_worker_context()->AddClient(
service_worker_version_id,
content::ServiceWorkerClientInfo(frame_tree_node_id));
content::GlobalFrameRoutingId render_frame_host_id =
frame_node_source()->CreateFrameNode(
render_process_id,
process_node_source()->GetProcessNode(render_process_id));
service_worker_context()->OnControlleeNavigationCommitted(
service_worker_version_id, frame_client_uuid, render_frame_host_id);
// Dedicated worker client.
blink::DedicatedWorkerToken dedicated_worker_token =
dedicated_worker_service()->CreateDedicatedWorker(render_process_id,
render_frame_host_id);
std::string dedicated_worker_client_uuid =
service_worker_context()->AddClient(
service_worker_version_id,
content::ServiceWorkerClientInfo(dedicated_worker_token));
// Shared worker client.
blink::SharedWorkerToken shared_worker_token =
shared_worker_service()->CreateSharedWorker(render_process_id);
std::string shared_worker_client_uuid = service_worker_context()->AddClient(
service_worker_version_id,
content::ServiceWorkerClientInfo(shared_worker_token));
// The service worker node doesn't even exist yet.
EXPECT_FALSE(GetServiceWorkerNode(service_worker_version_id));
// Check expectations on the graph.
CallOnGraphAndWait(base::BindLambdaForTesting(
[process_node = process_node_source()->GetProcessNode(render_process_id),
frame_node = frame_node_source()->GetFrameNode(render_frame_host_id),
dedicated_worker_node = GetDedicatedWorkerNode(dedicated_worker_token),
shared_worker_node =
GetSharedWorkerNode(shared_worker_token)](GraphImpl* graph) {
// The clients exists in the graph but they are not connected to the
// service worker.
EXPECT_TRUE(graph->NodeInGraph(frame_node));
EXPECT_TRUE(graph->NodeInGraph(dedicated_worker_node));
EXPECT_TRUE(graph->NodeInGraph(shared_worker_node));
// Note: Because a dedicated worker is always connected to a frame, this
// frame node actually has |dedicated_worker_node| as its sole client.
ASSERT_EQ(frame_node->child_worker_nodes().size(), 1u);
EXPECT_TRUE(base::Contains(frame_node->child_worker_nodes(),
dedicated_worker_node));
EXPECT_TRUE(dedicated_worker_node->child_workers().empty());
EXPECT_TRUE(shared_worker_node->child_workers().empty());
}));
// Now start the service worker.
service_worker_context()->StartServiceWorker(service_worker_version_id,
render_process_id);
// Check expectations on the graph.
CallOnGraphAndWait(base::BindLambdaForTesting(
[process_node = process_node_source()->GetProcessNode(render_process_id),
service_worker_node = GetServiceWorkerNode(service_worker_version_id),
frame_node = frame_node_source()->GetFrameNode(render_frame_host_id),
dedicated_worker_node = GetDedicatedWorkerNode(dedicated_worker_token),
shared_worker_node =
GetSharedWorkerNode(shared_worker_token)](GraphImpl* graph) {
EXPECT_TRUE(graph->NodeInGraph(service_worker_node));
EXPECT_EQ(service_worker_node->worker_type(),
WorkerNode::WorkerType::kService);
EXPECT_EQ(service_worker_node->process_node(), process_node);
EXPECT_TRUE(graph->NodeInGraph(frame_node));
EXPECT_TRUE(graph->NodeInGraph(dedicated_worker_node));
EXPECT_TRUE(graph->NodeInGraph(shared_worker_node));
// Now is it correctly hooked up.
EXPECT_TRUE(IsWorkerClient(service_worker_node, frame_node));
EXPECT_TRUE(IsWorkerClient(service_worker_node, dedicated_worker_node));
EXPECT_TRUE(IsWorkerClient(service_worker_node, shared_worker_node));
}));
// Stop the service worker. All the clients will be disconnected.
service_worker_context()->StopServiceWorker(service_worker_version_id);
// Check expectations on the graph.
CallOnGraphAndWait(base::BindLambdaForTesting(
[process_node = process_node_source()->GetProcessNode(render_process_id),
frame_node = frame_node_source()->GetFrameNode(render_frame_host_id),
dedicated_worker_node = GetDedicatedWorkerNode(dedicated_worker_token),
shared_worker_node =
GetSharedWorkerNode(shared_worker_token)](GraphImpl* graph) {
// The clients exists in the graph but they are not connected to the
// service worker.
EXPECT_TRUE(graph->NodeInGraph(frame_node));
EXPECT_TRUE(graph->NodeInGraph(dedicated_worker_node));
EXPECT_TRUE(graph->NodeInGraph(shared_worker_node));
// Note: Because a dedicated worker is always connected to a frame, this
// frame node actually has |dedicated_worker_node| as its sole client.
ASSERT_EQ(frame_node->child_worker_nodes().size(), 1u);
EXPECT_TRUE(base::Contains(frame_node->child_worker_nodes(),
dedicated_worker_node));
EXPECT_TRUE(dedicated_worker_node->child_workers().empty());
EXPECT_TRUE(shared_worker_node->child_workers().empty());
}));
// Disconnect and clean up the service worker and its clients
service_worker_context()->RemoveClient(service_worker_version_id,
shared_worker_client_uuid);
shared_worker_service()->DestroySharedWorker(shared_worker_token);
service_worker_context()->RemoveClient(service_worker_version_id,
dedicated_worker_client_uuid);
dedicated_worker_service()->DestroyDedicatedWorker(dedicated_worker_token);
service_worker_context()->RemoveClient(service_worker_version_id,
frame_client_uuid);
service_worker_context()->DestroyServiceWorker(service_worker_version_id);
}
TEST_F(WorkerWatcherTest, SharedWorkerCrossProcessClient) {
// Create the frame node.
int frame_process_id = process_node_source()->CreateProcessNode();
content::GlobalFrameRoutingId render_frame_host_id =
frame_node_source()->CreateFrameNode(
frame_process_id,
process_node_source()->GetProcessNode(frame_process_id));
// Create the worker in a different process.
int worker_process_id = process_node_source()->CreateProcessNode();
const blink::SharedWorkerToken& shared_worker_token =
shared_worker_service()->CreateSharedWorker(worker_process_id);
// Connect the frame to the worker.
shared_worker_service()->AddClient(shared_worker_token, render_frame_host_id);
// Check expectations on the graph.
CallOnGraphAndWait(base::BindLambdaForTesting(
[worker_process_node =
process_node_source()->GetProcessNode(worker_process_id),
worker_node = GetSharedWorkerNode(shared_worker_token),
client_process_node =
process_node_source()->GetProcessNode(frame_process_id),
client_frame_node = frame_node_source()->GetFrameNode(
render_frame_host_id)](GraphImpl* graph) {
EXPECT_TRUE(graph->NodeInGraph(worker_node));
EXPECT_EQ(worker_node->worker_type(), WorkerNode::WorkerType::kShared);
EXPECT_EQ(worker_node->process_node(), worker_process_node);
EXPECT_TRUE(IsWorkerClient(worker_node, client_frame_node));
}));
// Disconnect and clean up the shared worker.
shared_worker_service()->RemoveClient(shared_worker_token,
render_frame_host_id);
shared_worker_service()->DestroySharedWorker(shared_worker_token);
}
TEST_F(WorkerWatcherTest, SharedWorkerDiesAsServiceWorkerClient) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(
features::kServiceWorkerRelationshipsInGraph);
// Create the shared and service workers.
int render_process_id = process_node_source()->CreateProcessNode();
const blink::SharedWorkerToken& shared_worker_token =
shared_worker_service()->CreateSharedWorker(render_process_id);
int64_t service_worker_version_id =
service_worker_context()->CreateServiceWorker();
std::string service_worker_client_uuid = service_worker_context()->AddClient(
service_worker_version_id,
content::ServiceWorkerClientInfo(shared_worker_token));
service_worker_context()->StartServiceWorker(service_worker_version_id,
render_process_id);
// Check expectations on the graph.
CallOnGraphAndWait(base::BindLambdaForTesting(
[service_worker_node = GetServiceWorkerNode(service_worker_version_id),
shared_worker_node =
GetSharedWorkerNode(shared_worker_token)](GraphImpl* graph) {
EXPECT_TRUE(graph->NodeInGraph(service_worker_node));
EXPECT_EQ(service_worker_node->worker_type(),
WorkerNode::WorkerType::kService);
EXPECT_TRUE(graph->NodeInGraph(shared_worker_node));
EXPECT_EQ(shared_worker_node->worker_type(),
WorkerNode::WorkerType::kShared);
EXPECT_TRUE(IsWorkerClient(service_worker_node, shared_worker_node));
}));
// Destroy the shared worker while it still has a client registration
// against the service worker.
shared_worker_service()->DestroySharedWorker(shared_worker_token);
// Check expectations on the graph again.
CallOnGraphAndWait(base::BindLambdaForTesting(
[service_worker_node =
GetServiceWorkerNode(service_worker_version_id)](GraphImpl* graph) {
EXPECT_TRUE(graph->NodeInGraph(service_worker_node));
EXPECT_EQ(service_worker_node->worker_type(),
WorkerNode::WorkerType::kService);
EXPECT_TRUE(service_worker_node->client_workers().empty());
}));
// Issue the trailing service worker client removal.
service_worker_context()->RemoveClient(service_worker_version_id,
service_worker_client_uuid);
}
TEST_F(WorkerWatcherTest, OneSharedWorkerTwoClients) {
int render_process_id = process_node_source()->CreateProcessNode();
// Create the worker.
const blink::SharedWorkerToken& shared_worker_token =
shared_worker_service()->CreateSharedWorker(render_process_id);
// Create 2 client frame nodes and connect them to the worker.
content::GlobalFrameRoutingId render_frame_host_id_1 =
frame_node_source()->CreateFrameNode(
render_process_id,
process_node_source()->GetProcessNode(render_process_id));
shared_worker_service()->AddClient(shared_worker_token,
render_frame_host_id_1);
content::GlobalFrameRoutingId render_frame_host_id_2 =
frame_node_source()->CreateFrameNode(
render_process_id,
process_node_source()->GetProcessNode(render_process_id));
shared_worker_service()->AddClient(shared_worker_token,
render_frame_host_id_2);
// Check expectations on the graph.
CallOnGraphAndWait(base::BindLambdaForTesting(
[worker_node = GetSharedWorkerNode(shared_worker_token),
client_frame_node_1 =
frame_node_source()->GetFrameNode(render_frame_host_id_1),
client_frame_node_2 = frame_node_source()->GetFrameNode(
render_frame_host_id_2)](GraphImpl* graph) {
EXPECT_TRUE(graph->NodeInGraph(worker_node));
EXPECT_EQ(worker_node->worker_type(), WorkerNode::WorkerType::kShared);
// Check frame 1.
EXPECT_TRUE(IsWorkerClient(worker_node, client_frame_node_1));
// Check frame 2.
EXPECT_TRUE(IsWorkerClient(worker_node, client_frame_node_2));
}));
// Disconnect and clean up the shared worker.
shared_worker_service()->RemoveClient(shared_worker_token,
render_frame_host_id_1);
shared_worker_service()->RemoveClient(shared_worker_token,
render_frame_host_id_2);
shared_worker_service()->DestroySharedWorker(shared_worker_token);
}
TEST_F(WorkerWatcherTest, OneClientTwoSharedWorkers) {
int render_process_id = process_node_source()->CreateProcessNode();
// Create the frame node.
content::GlobalFrameRoutingId render_frame_host_id =
frame_node_source()->CreateFrameNode(
render_process_id,
process_node_source()->GetProcessNode(render_process_id));
// Create the 2 workers and connect them to the frame.
const blink::SharedWorkerToken& shared_worker_token_1 =
shared_worker_service()->CreateSharedWorker(render_process_id);
shared_worker_service()->AddClient(shared_worker_token_1,
render_frame_host_id);
const blink::SharedWorkerToken& shared_worker_token_2 =
shared_worker_service()->CreateSharedWorker(render_process_id);
shared_worker_service()->AddClient(shared_worker_token_2,
render_frame_host_id);
// Check expectations on the graph.
CallOnGraphAndWait(base::BindLambdaForTesting(
[worker_node_1 = GetSharedWorkerNode(shared_worker_token_1),
worker_node_2 = GetSharedWorkerNode(shared_worker_token_2),
client_frame_node = frame_node_source()->GetFrameNode(
render_frame_host_id)](GraphImpl* graph) {
// Check worker 1.
EXPECT_TRUE(graph->NodeInGraph(worker_node_1));
EXPECT_EQ(worker_node_1->worker_type(),
WorkerNode::WorkerType::kShared);
EXPECT_TRUE(IsWorkerClient(worker_node_1, client_frame_node));
// Check worker 2.
EXPECT_TRUE(graph->NodeInGraph(worker_node_2));
EXPECT_EQ(worker_node_2->worker_type(),
WorkerNode::WorkerType::kShared);
EXPECT_TRUE(IsWorkerClient(worker_node_2, client_frame_node));
}));
// Disconnect and clean up the shared workers.
shared_worker_service()->RemoveClient(shared_worker_token_1,
render_frame_host_id);
shared_worker_service()->DestroySharedWorker(shared_worker_token_1);
shared_worker_service()->RemoveClient(shared_worker_token_2,
render_frame_host_id);
shared_worker_service()->DestroySharedWorker(shared_worker_token_2);
}
void WorkerWatcherTest::TestFrameDestroyed(
bool enable_service_worker_relationships) {
base::test::ScopedFeatureList feature_list;
if (enable_service_worker_relationships) {
feature_list.InitAndEnableFeature(
features::kServiceWorkerRelationshipsInGraph);
} else {
feature_list.InitAndDisableFeature(
features::kServiceWorkerRelationshipsInGraph);
}
int render_process_id = process_node_source()->CreateProcessNode();
// Create the frame node.
int frame_tree_node_id = GenerateNextId();
content::GlobalFrameRoutingId render_frame_host_id =
frame_node_source()->CreateFrameNode(
render_process_id,
process_node_source()->GetProcessNode(render_process_id));
// Create a worker of each type.
const blink::DedicatedWorkerToken& dedicated_worker_token =
dedicated_worker_service()->CreateDedicatedWorker(render_process_id,
render_frame_host_id);
const blink::SharedWorkerToken& shared_worker_token =
shared_worker_service()->CreateSharedWorker(render_process_id);
int64_t service_worker_version_id =
service_worker_context()->CreateServiceWorker();
service_worker_context()->StartServiceWorker(service_worker_version_id,
render_process_id);
// Connect the frame to the shared worker and the service worker. Note that it
// is already connected to the dedicated worker.
shared_worker_service()->AddClient(shared_worker_token, render_frame_host_id);
std::string service_worker_client_uuid = service_worker_context()->AddClient(
service_worker_version_id,
content::ServiceWorkerClientInfo(frame_tree_node_id));
service_worker_context()->OnControlleeNavigationCommitted(
service_worker_version_id, service_worker_client_uuid,
render_frame_host_id);
// Check that everything is wired up correctly.
CallOnGraphAndWait(base::BindLambdaForTesting(
[&enable_service_worker_relationships,
dedicated_worker_node = GetDedicatedWorkerNode(dedicated_worker_token),
shared_worker_node = GetSharedWorkerNode(shared_worker_token),
service_worker_node = GetServiceWorkerNode(service_worker_version_id),
client_frame_node = frame_node_source()->GetFrameNode(
render_frame_host_id)](GraphImpl* graph) {
EXPECT_TRUE(graph->NodeInGraph(dedicated_worker_node));
EXPECT_TRUE(graph->NodeInGraph(shared_worker_node));
EXPECT_TRUE(graph->NodeInGraph(service_worker_node));
EXPECT_TRUE(IsWorkerClient(dedicated_worker_node, client_frame_node));
EXPECT_TRUE(IsWorkerClient(shared_worker_node, client_frame_node));
EXPECT_EQ(enable_service_worker_relationships,
IsWorkerClient(service_worker_node, client_frame_node));
}));
frame_node_source()->DeleteFrameNode(render_frame_host_id);
// Check that the workers are no longer connected to the deleted frame.
CallOnGraphAndWait(base::BindLambdaForTesting(
[dedicated_worker_node = GetDedicatedWorkerNode(dedicated_worker_token),
shared_worker_node = GetSharedWorkerNode(shared_worker_token),
service_worker_node =
GetServiceWorkerNode(service_worker_version_id)](GraphImpl* graph) {
EXPECT_TRUE(graph->NodeInGraph(dedicated_worker_node));
EXPECT_TRUE(graph->NodeInGraph(shared_worker_node));
EXPECT_TRUE(graph->NodeInGraph(service_worker_node));
EXPECT_TRUE(dedicated_worker_node->client_frames().empty());
EXPECT_TRUE(shared_worker_node->client_frames().empty());
EXPECT_TRUE(service_worker_node->client_frames().empty());
}));
// Clean up. The watcher is still expecting a worker removed notification.
service_worker_context()->RemoveClient(service_worker_version_id,
service_worker_client_uuid);
service_worker_context()->StopServiceWorker(service_worker_version_id);
service_worker_context()->DestroyServiceWorker(service_worker_version_id);
shared_worker_service()->RemoveClient(shared_worker_token,
render_frame_host_id);
shared_worker_service()->DestroySharedWorker(shared_worker_token);
dedicated_worker_service()->DestroyDedicatedWorker(dedicated_worker_token);
}
TEST_F(WorkerWatcherTest, FrameDestroyed) {
TestFrameDestroyed(false);
}
TEST_F(WorkerWatcherTest, FrameDestroyedWithServiceWorkerRelationships) {
TestFrameDestroyed(true);
}
} // namespace performance_manager