1use std::sync::Arc;
6use std::sync::atomic::AtomicBool;
7
8use crossbeam_channel::{Sender, unbounded};
9use devtools_traits::{DevtoolsPageInfo, ScriptToDevtoolsControlMsg, WorkerId};
10use dom_struct::dom_struct;
11use js::context::JSContext;
12use js::rust::HandleObject;
13use net_traits::request::Referrer;
14use script_bindings::reflector::reflect_dom_object_with_proto_and_cx;
15use servo_base::generic_channel;
16use servo_constellation_traits::WorkerScriptLoadOrigin;
17use uuid::Uuid;
18
19use crate::conversions::Convert;
20use crate::dom::abstractworker::SimpleWorkerErrorHandler;
21use crate::dom::bindings::codegen::Bindings::SharedWorkerBinding::{
22 SharedWorkerMethods, SharedWorkerOptions,
23};
24use crate::dom::bindings::codegen::UnionTypes::{
25 StringOrSharedWorkerOptions, TrustedScriptURLOrUSVString,
26};
27use crate::dom::bindings::error::{Error, Fallible};
28use crate::dom::bindings::inheritance::Castable;
29use crate::dom::bindings::refcounted::Trusted;
30use crate::dom::bindings::reflector::DomGlobal;
31use crate::dom::bindings::root::{Dom, DomRoot};
32use crate::dom::bindings::trace::CustomTraceable;
33use crate::dom::bindings::transferable::Transferable;
34use crate::dom::eventtarget::EventTarget;
35use crate::dom::globalscope::GlobalScope;
36use crate::dom::messageport::MessagePort;
37use crate::dom::sharedworkerglobalscope::{
38 SharedWorkerControlMsg, SharedWorkerGlobalScope, SharedWorkerScriptMsg,
39};
40use crate::dom::trustedtypes::trustedscripturl::TrustedScriptURL;
41use crate::dom::window::Window;
42use crate::dom::workerglobalscope::prepare_workerscope_init;
43use crate::script_runtime::CanGc;
44use crate::task::TaskOnce;
45use crate::url::ensure_blob_referenced_by_url_is_kept_alive;
46
47#[dom_struct]
49pub(crate) struct SharedWorker {
50 eventtarget: EventTarget,
51 port: Dom<MessagePort>,
52 _control_sender: Sender<SharedWorkerControlMsg>,
53}
54
55pub(crate) type TrustedSharedWorkerAddress = Trusted<SharedWorker>;
56
57impl SharedWorker {
58 fn new_inherited(
59 port: &MessagePort,
60 control_sender: Sender<SharedWorkerControlMsg>,
61 ) -> SharedWorker {
62 SharedWorker {
63 eventtarget: EventTarget::new_inherited(),
64 port: Dom::from_ref(port),
65 _control_sender: control_sender,
66 }
67 }
68
69 fn new(
70 global: &GlobalScope,
71 proto: Option<HandleObject>,
72 port: &MessagePort,
73 control_sender: Sender<SharedWorkerControlMsg>,
74 cx: &mut js::context::JSContext,
75 ) -> DomRoot<SharedWorker> {
76 reflect_dom_object_with_proto_and_cx(
77 Box::new(SharedWorker::new_inherited(port, control_sender)),
78 global,
79 proto,
80 cx,
81 )
82 }
83
84 pub(crate) fn dispatch_simple_error(cx: &mut JSContext, address: TrustedSharedWorkerAddress) {
85 let worker = address.root();
86 worker.upcast().fire_event(cx, atom!("error"));
87 }
88
89 pub(crate) fn enable_outside_port_message_queue(
91 address: TrustedSharedWorkerAddress,
92 cx: &mut JSContext,
93 ) {
94 let worker = address.root();
95 let global = worker.global();
96 global.start_message_port(cx, worker.port.message_port_id());
98 }
99}
100
101impl SharedWorkerMethods<crate::DomTypeHolder> for SharedWorker {
102 fn Constructor(
104 cx: &mut JSContext,
105 window: &Window,
106 proto: Option<HandleObject>,
107 script_url: TrustedScriptURLOrUSVString,
108 options: StringOrSharedWorkerOptions,
109 ) -> Fallible<DomRoot<SharedWorker>> {
110 let global = window.upcast::<GlobalScope>();
111
112 let compliant_script_url = TrustedScriptURL::get_trusted_type_compliant_string(
116 cx,
117 global,
118 script_url,
119 "SharedWorker constructor",
120 )?;
121
122 let worker_options = match options {
126 StringOrSharedWorkerOptions::String(name) => {
127 let mut options = SharedWorkerOptions::empty();
128 options.parent.name = name;
129 options
130 },
131 StringOrSharedWorkerOptions::SharedWorkerOptions(options) => options,
132 };
133 let worker_name = worker_options.parent.name.clone();
134 let worker_type = worker_options.parent.type_;
135 let credentials = worker_options.parent.credentials.convert();
136
137 let Ok(worker_url) = global
144 .encoding_parse_a_url(&compliant_script_url.str())
145 .map(|url| ensure_blob_referenced_by_url_is_kept_alive(global, url))
146 else {
147 return Err(Error::Syntax(None));
148 };
149
150 let outside_port = MessagePort::new(global, CanGc::from_cx(cx));
152 global.track_message_port(&outside_port, None);
153
154 let (control_sender, control_receiver) = unbounded();
155
156 let _caller_is_secure_context = global.is_secure_context();
160 let worker = SharedWorker::new(global, proto, &outside_port, control_sender, cx);
163 let worker_addr = Trusted::new(&*worker);
164 let parent_event_loop_sender = global
165 .event_loop_sender()
166 .expect("Window global must have an event loop sender");
167
168 let (sender, receiver) = unbounded();
169 let closing = Arc::new(AtomicBool::new(false));
170
171 let inside_port = MessagePort::new(global, CanGc::from_cx(cx));
178 global.track_message_port(&inside_port, None);
179 global.entangle_ports(
180 *outside_port.message_port_id(),
181 *inside_port.message_port_id(),
182 );
183 let (_, inside_port_impl) = inside_port.transfer(cx)?;
184
185 let worker_load_origin = WorkerScriptLoadOrigin {
186 referrer_url: match global.get_referrer() {
187 Referrer::Client(url) => Some(url),
188 Referrer::ReferrerUrl(url) => Some(url),
189 _ => None,
190 },
191 referrer_policy: global.get_referrer_policy(),
192 pipeline_id: global.pipeline_id(),
193 };
194
195 let (devtools_sender, devtools_receiver) = generic_channel::channel().unwrap();
196 let worker_id = WorkerId(Uuid::new_v4());
197 if let Some(chan) = global.devtools_chan() {
198 let webview_id = global
199 .webview_id()
200 .expect("Window global must have a WebViewId");
201 let page_info = DevtoolsPageInfo {
202 title: format!("SharedWorker for {}", worker_url.url()),
203 url: worker_url.url(),
204 is_top_level_global: false,
205 is_service_worker: false,
206 };
207 let _ = chan.send(ScriptToDevtoolsControlMsg::NewGlobal(
208 (
209 window.window_proxy().browsing_context_id(),
210 global.pipeline_id(),
211 Some(worker_id),
212 webview_id,
213 ),
214 devtools_sender.clone(),
215 page_info,
216 ));
217 }
218
219 let init = prepare_workerscope_init(
220 global,
221 Some(devtools_sender),
222 Some(worker_id),
223 window.webgl_chan_value(),
224 );
225
226 let (context_sender, _context_receiver) = unbounded();
227
228 let _join_handle = SharedWorkerGlobalScope::run_shared_worker_scope(
229 init,
230 worker_name,
231 worker_type,
232 worker_url,
233 worker_addr,
234 parent_event_loop_sender,
235 devtools_receiver,
236 sender.clone(),
237 receiver,
238 worker_load_origin,
239 closing,
240 #[cfg(feature = "webgpu")]
241 global.wgpu_id_hub(),
242 control_receiver,
243 context_sender,
244 credentials,
245 global.insecure_requests_policy(),
246 global.policy_container(),
247 global.font_context().cloned(),
248 );
249
250 sender
253 .send(SharedWorkerScriptMsg::Connect(inside_port_impl))
254 .expect("SharedWorker failed to receive its initial connection");
255
256 Ok(worker)
257 }
258
259 fn Port(&self) -> DomRoot<MessagePort> {
261 DomRoot::from_ref(&*self.port)
263 }
264
265 event_handler!(error, GetOnerror, SetOnerror);
267}
268
269impl TaskOnce for SimpleWorkerErrorHandler<SharedWorker> {
270 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
271 fn run_once(self, cx: &mut JSContext) {
272 SharedWorker::dispatch_simple_error(cx, self.addr);
273 }
274}