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,
private:
friend class WorkerWatcherTest;
// Posts a task to the PM graph to connect/disconnect |worker_node| with the
// frame node associated to |client_render_frame_host_id|.
void ConnectFrameClient(
// Adds a connection between |worker_node| and the frame node represented by
// |client_render_frame_host_id|. Connects them in the graph when the first
// connection is added.
void AddFrameClientConnection(
WorkerNodeImpl* worker_node,
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,
content::GlobalFrameRoutingId client_render_frame_host_id);
......@@ -157,12 +161,24 @@ class WorkerWatcher : public content::DedicatedWorkerService::Observer,
content::GlobalFrameRoutingId render_frame_host_id,
FrameNodeImpl* frame_node);
// Inserts/removes |child_worker_node| into the set of child workers of a
// frame. Returns true if this is the first child added to that frame.
bool AddChildWorker(content::GlobalFrameRoutingId render_frame_host_id,
WorkerNodeImpl* child_worker_node);
bool RemoveChildWorker(content::GlobalFrameRoutingId render_frame_host_id,
WorkerNodeImpl* child_worker_node);
// Adds/removes a connection to |child_worker_node| in the set of child
// workers of a frame.
// On exit |is_first_child_worker| is true if this is the first child worker
// added to the frame and |is_first_child_worker_connection| is true if
// this was the first connection from the frame and |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.
WorkerNodeImpl* GetDedicatedWorkerNode(
......@@ -221,12 +237,14 @@ class WorkerWatcher : public content::DedicatedWorkerService::Observer,
base::flat_map<std::string /*client_uuid*/, ServiceWorkerClient>>
service_worker_clients_;
// Maps each frame to the workers that this frame is a client of in the graph.
// This is used when a frame is torn down before the
// OnBeforeWorkerTerminated() is received, to ensure the deletion of the
// worker nodes in the right order (workers before frames).
base::flat_map<content::GlobalFrameRoutingId, base::flat_set<WorkerNodeImpl*>>
frame_node_child_workers_;
// Maps each frame to the number of connections to each worker that this frame
// is a client of in the graph. Note that normally there's a single connection
// from a frame to a worker, except in rare circumstances where it appears
// that a single frame can have multiple "controllee" relationships to the
// same service worker. This is represented as a single edge in the PM graph.
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.
base::flat_map<blink::DedicatedWorkerToken, base::flat_set<WorkerNodeImpl*>>
......
......@@ -979,6 +979,104 @@ TEST_F(WorkerWatcherTest, ServiceWorkerFrameClientOfTwoWorkers) {
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.
......
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