Skip to main content

devtools/actors/
worker.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::sync::Arc;
6
7use atomic_refcell::AtomicRefCell;
8use devtools_traits::DevtoolScriptControlMsg;
9use devtools_traits::DevtoolScriptControlMsg::WantsLiveNotifications;
10use malloc_size_of_derive::MallocSizeOf;
11use rustc_hash::FxHashSet;
12use serde::Serialize;
13use serde_json::{Map, Value};
14use servo_base::generic_channel::GenericSender;
15use servo_base::id::{TEST_PIPELINE_ID, WorkerId};
16use servo_url::ServoUrl;
17
18use crate::StreamId;
19use crate::actor::{Actor, ActorEncode, ActorError, ActorRegistry, new_actor_name};
20use crate::protocol::{ClientRequest, JsonPacketStream};
21use crate::resource::ResourceAvailable;
22
23#[derive(Clone, Copy, MallocSizeOf)]
24#[expect(dead_code)]
25pub enum WorkerType {
26    Dedicated = 0,
27    Shared = 1,
28    Service = 2,
29}
30
31#[derive(MallocSizeOf)]
32pub(crate) struct WorkerTargetActor {
33    pub name: String,
34    pub console_name: String,
35    pub thread_name: String,
36    pub worker_id: WorkerId,
37    pub url: ServoUrl,
38    pub type_: WorkerType,
39    pub script_sender: GenericSender<DevtoolScriptControlMsg>,
40    pub streams: AtomicRefCell<FxHashSet<StreamId>>,
41}
42
43impl ResourceAvailable for WorkerTargetActor {
44    fn actor_name(&self) -> String {
45        self.name.clone()
46    }
47}
48
49impl WorkerTargetActor {
50    pub fn register(
51        registry: &ActorRegistry,
52        console_name: String,
53        thread_name: String,
54        worker_id: WorkerId,
55        url: ServoUrl,
56        worker_type: WorkerType,
57        script_sender: GenericSender<DevtoolScriptControlMsg>,
58    ) -> Arc<Self> {
59        let name = new_actor_name::<Self>();
60        let actor = Self {
61            name,
62            console_name,
63            thread_name,
64            worker_id,
65            url,
66            type_: worker_type,
67            script_sender,
68            streams: Default::default(),
69        };
70        registry.register::<Self>(actor)
71    }
72}
73
74impl Actor for WorkerTargetActor {
75    fn name(&self) -> &str {
76        &self.name
77    }
78    fn handle_message(
79        &self,
80        mut request: ClientRequest,
81        _registry: &ActorRegistry,
82        msg_type: &str,
83        _msg: &Map<String, Value>,
84        stream_id: StreamId,
85    ) -> Result<(), ActorError> {
86        match msg_type {
87            "attach" => {
88                let msg = AttachedReply {
89                    from: self.name().into(),
90                    type_: "attached".to_owned(),
91                    url: self.url.as_str().to_owned(),
92                };
93                // FIXME: we don’t send an actual reply (message without type), which seems to be a bug?
94                request.write_json_packet(&msg)?;
95                self.streams.borrow_mut().insert(stream_id);
96                // FIXME: fix messages to not require forging a pipeline for worker messages
97                self.script_sender
98                    .send(WantsLiveNotifications(TEST_PIPELINE_ID, true))
99                    .unwrap();
100            },
101
102            "connect" => {
103                let msg = ConnectReply {
104                    from: self.name().into(),
105                    type_: "connected".to_owned(),
106                    thread_actor: self.thread_name.clone(),
107                    console_actor: self.console_name.clone(),
108                };
109                // FIXME: we don’t send an actual reply (message without type), which seems to be a bug?
110                request.write_json_packet(&msg)?;
111            },
112
113            "detach" => {
114                let msg = DetachedReply {
115                    from: self.name().into(),
116                    type_: "detached".to_string(),
117                };
118                self.cleanup(stream_id);
119                // FIXME: we don’t send an actual reply (message without type), which seems to be a bug?
120                request.write_json_packet(&msg)?;
121            },
122
123            "getPushSubscription" => {
124                let msg = GetPushSubscriptionReply {
125                    from: self.name().into(),
126                    subscription: None,
127                };
128                request.reply_final(&msg)?
129            },
130
131            _ => return Err(ActorError::UnrecognizedPacketType),
132        };
133        Ok(())
134    }
135
136    fn cleanup(&self, stream_id: StreamId) {
137        self.streams.borrow_mut().remove(&stream_id);
138        if self.streams.borrow().is_empty() {
139            self.script_sender
140                .send(WantsLiveNotifications(TEST_PIPELINE_ID, false))
141                .unwrap();
142        }
143    }
144}
145
146#[derive(Serialize)]
147struct GetPushSubscriptionReply {
148    from: String,
149    subscription: Option<()>,
150}
151
152#[derive(Serialize)]
153struct DetachedReply {
154    from: String,
155    #[serde(rename = "type")]
156    type_: String,
157}
158
159#[derive(Serialize)]
160struct AttachedReply {
161    from: String,
162    #[serde(rename = "type")]
163    type_: String,
164    url: String,
165}
166
167#[derive(Serialize)]
168#[serde(rename_all = "camelCase")]
169struct ConnectReply {
170    from: String,
171    #[serde(rename = "type")]
172    type_: String,
173    thread_actor: String,
174    console_actor: String,
175}
176
177#[derive(Serialize)]
178#[serde(rename_all = "camelCase")]
179struct WorkerTraits {
180    is_parent_intercept_enabled: bool,
181    supports_top_level_target_flag: bool,
182}
183
184#[derive(Serialize)]
185#[serde(rename_all = "camelCase")]
186pub(crate) struct WorkerTargetActorMsg {
187    actor: String,
188    console_actor: String,
189    thread_actor: String,
190    id: String,
191    url: String,
192    traits: WorkerTraits,
193    #[serde(rename = "type")]
194    type_: u32,
195    #[serde(rename = "targetType")]
196    target_type: String,
197}
198
199impl ActorEncode<WorkerTargetActorMsg> for WorkerTargetActor {
200    fn encode(&self, _: &ActorRegistry) -> WorkerTargetActorMsg {
201        WorkerTargetActorMsg {
202            actor: self.name().into(),
203            console_actor: self.console_name.clone(),
204            thread_actor: self.thread_name.clone(),
205            id: self.worker_id.0.to_string(),
206            url: self.url.to_string(),
207            traits: WorkerTraits {
208                is_parent_intercept_enabled: false,
209                supports_top_level_target_flag: false,
210            },
211            type_: self.type_ as u32,
212            target_type: match self.type_ {
213                WorkerType::Service => "service_worker",
214                _ => "worker",
215            }
216            .to_string(),
217        }
218    }
219}