Commit b5f1d321 authored by Sigurdur Asgeirsson's avatar Sigurdur Asgeirsson Committed by Chromium LUCI CQ

PM: Keep track of frame->worker connection counts.

This allows the WorkerWatcher to cope with the case where there are
multiple Frame<->Worker relationships in existence at the same time
for a single Frame, Worker pair. Multiple relationships are
represented as a single edge in the graph, which is added/removed
on 0<->1 count transitions.

Bug: 1143281
Change-Id: I032f62e7440ce4e4daf7495085f7c75877cb5188
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2624868
Commit-Queue: Sigurður Ásgeirsson <siggi@chromium.org>
Commit-Queue: Patrick Monette <pmonette@chromium.org>
Auto-Submit: Sigurður Ásgeirsson <siggi@chromium.org>
Reviewed-by: default avatarPatrick Monette <pmonette@chromium.org>
Cr-Commit-Position: refs/heads/master@{#843549}
parent eacb9456
...@@ -118,12 +118,16 @@ class WorkerWatcher : public content::DedicatedWorkerService::Observer, ...@@ -118,12 +118,16 @@ class WorkerWatcher : public content::DedicatedWorkerService::Observer,
private: private:
friend class WorkerWatcherTest; friend class WorkerWatcherTest;
// Posts a task to the PM graph to connect/disconnect |worker_node| with the // Adds a connection between |worker_node| and the frame node represented by
// frame node associated to |client_render_frame_host_id|. // |client_render_frame_host_id|. Connects them in the graph when the first
void ConnectFrameClient( // connection is added.
void AddFrameClientConnection(
WorkerNodeImpl* worker_node, WorkerNodeImpl* worker_node,
content::GlobalFrameRoutingId client_render_frame_host_id); content::GlobalFrameRoutingId client_render_frame_host_id);
void DisconnectFrameClient( // Removes a connection between |worker_node| and the frame node represented
// by |client_render_frame_host_id|. Disconnects them in the graph when the
// last connection is removed.
void RemoveFrameClientConnection(
WorkerNodeImpl* worker_node, WorkerNodeImpl* worker_node,
content::GlobalFrameRoutingId client_render_frame_host_id); content::GlobalFrameRoutingId client_render_frame_host_id);
...@@ -157,12 +161,24 @@ class WorkerWatcher : public content::DedicatedWorkerService::Observer, ...@@ -157,12 +161,24 @@ class WorkerWatcher : public content::DedicatedWorkerService::Observer,
content::GlobalFrameRoutingId render_frame_host_id, content::GlobalFrameRoutingId render_frame_host_id,
FrameNodeImpl* frame_node); FrameNodeImpl* frame_node);
// Inserts/removes |child_worker_node| into the set of child workers of a // Adds/removes a connection to |child_worker_node| in the set of child
// frame. Returns true if this is the first child added to that frame. // workers of a frame.
bool AddChildWorker(content::GlobalFrameRoutingId render_frame_host_id, // On exit |is_first_child_worker| is true if this is the first child worker
WorkerNodeImpl* child_worker_node); // added to the frame and |is_first_child_worker_connection| is true if
bool RemoveChildWorker(content::GlobalFrameRoutingId render_frame_host_id, // this was the first connection from the frame and |child_worker_node|.
WorkerNodeImpl* child_worker_node); // Conversely |was_last_child_worker| is true if this was the last client
// worker removed, and |was_last_child_worker_connection| is true if this
// removed the last connection between the frame and |child_worker_node|.
void AddChildWorkerConnection(
content::GlobalFrameRoutingId render_frame_host_id,
WorkerNodeImpl* child_worker_node,
bool* is_first_child_worker,
bool* is_first_child_worker_connection);
void RemoveChildWorkerConnection(
content::GlobalFrameRoutingId render_frame_host_id,
WorkerNodeImpl* child_worker_node,
bool* was_last_child_worker,
bool* was_last_child_worker_connection);
// Helper functions to retrieve an existing worker node. // Helper functions to retrieve an existing worker node.
WorkerNodeImpl* GetDedicatedWorkerNode( WorkerNodeImpl* GetDedicatedWorkerNode(
...@@ -221,12 +237,14 @@ class WorkerWatcher : public content::DedicatedWorkerService::Observer, ...@@ -221,12 +237,14 @@ class WorkerWatcher : public content::DedicatedWorkerService::Observer,
base::flat_map<std::string /*client_uuid*/, ServiceWorkerClient>> base::flat_map<std::string /*client_uuid*/, ServiceWorkerClient>>
service_worker_clients_; service_worker_clients_;
// Maps each frame to the workers that this frame is a client of in the graph. // Maps each frame to the number of connections to each worker that this frame
// This is used when a frame is torn down before the // is a client of in the graph. Note that normally there's a single connection
// OnBeforeWorkerTerminated() is received, to ensure the deletion of the // from a frame to a worker, except in rare circumstances where it appears
// worker nodes in the right order (workers before frames). // that a single frame can have multiple "controllee" relationships to the
base::flat_map<content::GlobalFrameRoutingId, base::flat_set<WorkerNodeImpl*>> // same service worker. This is represented as a single edge in the PM graph.
frame_node_child_workers_; using WorkerNodeConnections = base::flat_map<WorkerNodeImpl*, size_t>;
base::flat_map<content::GlobalFrameRoutingId, WorkerNodeConnections>
frame_node_child_worker_connections_;
// Maps each dedicated worker to all its child workers. // Maps each dedicated worker to all its child workers.
base::flat_map<blink::DedicatedWorkerToken, base::flat_set<WorkerNodeImpl*>> base::flat_map<blink::DedicatedWorkerToken, base::flat_set<WorkerNodeImpl*>>
......
...@@ -979,6 +979,104 @@ TEST_F(WorkerWatcherTest, ServiceWorkerFrameClientOfTwoWorkers) { ...@@ -979,6 +979,104 @@ TEST_F(WorkerWatcherTest, ServiceWorkerFrameClientOfTwoWorkers) {
second_service_worker_version_id); 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 // 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 // worker is created but it's navigation is never committed before the
// FrameTreeNode is destroyed. // FrameTreeNode is destroyed.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment