Skip to main content

script/dom/workers/
sharedworker.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;
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/// <https://html.spec.whatwg.org/multipage/#shared-workers-and-the-sharedworker-interface>
48#[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    /// Step 11 of onComplete of <https://html.spec.whatwg.org/multipage/#run-a-worker>
90    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        // Enable outside port's port message queue.
97        global.start_message_port(cx, worker.port.message_port_id());
98    }
99}
100
101impl SharedWorkerMethods<crate::DomTypeHolder> for SharedWorker {
102    /// <https://html.spec.whatwg.org/multipage/#dom-sharedworker>
103    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        // Step 1. Let compliantScriptURL be the result of invoking the get trusted type
113        // compliant string algorithm with TrustedScriptURL, this's relevant global object,
114        // scriptURL, "SharedWorker constructor", and "script".
115        let compliant_script_url = TrustedScriptURL::get_trusted_type_compliant_string(
116            cx,
117            global,
118            script_url,
119            "SharedWorker constructor",
120        )?;
121
122        // Step 2. If options is a DOMString, set options to a new WorkerOptions
123        // dictionary whose name member is set to the value of options and whose other
124        // members are set to their default values.
125        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        // Step 3. Let outsideSettings be this's relevant settings object.
138        // (outsideSettings is `global` throughout.)
139
140        // Step 4. Let urlRecord be the result of encoding-parsing a URL given
141        // compliantScriptURL, relative to outsideSettings.
142        // Step 5. If urlRecord is failure, then throw a "SyntaxError" DOMException.
143        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        // Step 6. Let outsidePort be a new MessagePort in outsideSettings's realm.
151        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        // Step 7. Set this's port to outsidePort.
157        // Step 8. Let callerIsSecureContext be true if outsideSettings is a secure
158        // context; otherwise, false.
159        let _caller_is_secure_context = global.is_secure_context();
160        // TODO Step 9.
161        // Step 10. Let worker be this.
162        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        // Step 11. Enqueue the following steps to the shared worker manager.
172        // TODO Steps 12-16.
173        // Until then, SharedWorker construction always takes the fresh-worker
174        // branch below instead of reusing an existing SharedWorkerGlobalScope.
175        // Step 17. Otherwise, in parallel, run a worker given worker, urlRecord,
176        // outsideSettings, outsidePort, and options.
177        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        // Implementation hook for onComplete step 13: the receiving side fires
251        // the connect event immediately, or defers it until execution-ready.
252        sender
253            .send(SharedWorkerScriptMsg::Connect(inside_port_impl))
254            .expect("SharedWorker failed to receive its initial connection");
255
256        Ok(worker)
257    }
258
259    /// <https://html.spec.whatwg.org/multipage/#dom-sharedworker-port>
260    fn Port(&self) -> DomRoot<MessagePort> {
261        // The port getter steps are to return this's port.
262        DomRoot::from_ref(&*self.port)
263    }
264
265    // <https://html.spec.whatwg.org/multipage/#handler-abstractworker-onerror>
266    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}