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