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::atomic::{AtomicBool, Ordering};
6use std::sync::{Arc, Condvar, LazyLock, Mutex};
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 malloc_size_of_derive::MallocSizeOf;
14use net_traits::pub_domains::reg_suffix;
15use net_traits::request::{CredentialsMode, Referrer};
16use script_bindings::reflector::reflect_dom_object_with_proto_and_cx;
17use servo_base::generic_channel;
18use servo_constellation_traits::{MessagePortImpl, WorkerScriptLoadOrigin};
19use servo_url::{Host, ImmutableOrigin, ServoUrl};
20use uuid::Uuid;
21
22use crate::conversions::Convert;
23use crate::dom::abstractworker::SimpleWorkerErrorHandler;
24use crate::dom::bindings::codegen::Bindings::SharedWorkerBinding::{
25    SharedWorkerMethods, SharedWorkerOptions,
26};
27use crate::dom::bindings::codegen::Bindings::WorkerBinding::WorkerType;
28use crate::dom::bindings::codegen::UnionTypes::{
29    StringOrSharedWorkerOptions, TrustedScriptURLOrUSVString,
30};
31use crate::dom::bindings::error::{Error, Fallible};
32use crate::dom::bindings::inheritance::Castable;
33use crate::dom::bindings::refcounted::Trusted;
34use crate::dom::bindings::reflector::DomGlobal;
35use crate::dom::bindings::root::{Dom, DomRoot};
36use crate::dom::bindings::trace::CustomTraceable;
37use crate::dom::bindings::transferable::Transferable;
38use crate::dom::eventtarget::EventTarget;
39use crate::dom::globalscope::GlobalScope;
40use crate::dom::messageport::MessagePort;
41use crate::dom::sharedworkerglobalscope::{
42    SharedWorkerControlMsg, SharedWorkerGlobalScope, SharedWorkerScriptMsg,
43};
44use crate::dom::trustedtypes::trustedscripturl::TrustedScriptURL;
45use crate::dom::window::Window;
46use crate::dom::workerglobalscope::prepare_workerscope_init;
47use crate::task::TaskOnce;
48use crate::url::ensure_blob_referenced_by_url_is_kept_alive;
49
50/// <https://html.spec.whatwg.org/multipage/#shared-workers-and-the-sharedworker-interface>
51#[dom_struct]
52pub(crate) struct SharedWorker {
53    eventtarget: EventTarget,
54    port: Dom<MessagePort>,
55    _control_sender: Sender<SharedWorkerControlMsg>,
56}
57
58pub(crate) type TrustedSharedWorkerAddress = Trusted<SharedWorker>;
59
60/// Key used by the script-side SharedWorker registry.
61#[derive(Clone)]
62struct SharedWorkerKey {
63    storage_key: SharedWorkerStorageKey,
64    constructor_origin: ImmutableOrigin,
65    constructor_url: ServoUrl,
66    name: String,
67}
68
69impl SharedWorkerKey {
70    fn matches(
71        &self,
72        storage_key: &SharedWorkerStorageKey,
73        constructor_origin: &ImmutableOrigin,
74        constructor_url: &ServoUrl,
75        name: &str,
76    ) -> bool {
77        self.storage_key == *storage_key &&
78            self.constructor_origin == *constructor_origin &&
79            self.constructor_url == *constructor_url &&
80            self.name == name
81    }
82}
83
84#[derive(Clone, Eq, MallocSizeOf, PartialEq)]
85enum SharedWorkerSite {
86    Opaque(ImmutableOrigin),
87    Tuple { scheme: String, host: Host },
88}
89
90impl SharedWorkerSite {
91    /// <https://html.spec.whatwg.org/multipage/#obtain-a-site>
92    fn from_origin(origin: ImmutableOrigin) -> Self {
93        match origin {
94            // 1. If origin is an opaque origin, then return origin.
95            ImmutableOrigin::Opaque(_) => SharedWorkerSite::Opaque(origin),
96            // 3. Return (origin's scheme, origin's host's registrable domain).
97            ImmutableOrigin::Tuple(scheme, Host::Domain(domain), _) => SharedWorkerSite::Tuple {
98                scheme,
99                host: Host::Domain(reg_suffix(&domain).to_owned()),
100            },
101            // 2. If origin's host's registrable domain is null, then return
102            // (origin's scheme, origin's host).
103            ImmutableOrigin::Tuple(scheme, host, _) => SharedWorkerSite::Tuple { scheme, host },
104        }
105    }
106}
107
108#[derive(Clone, Eq, MallocSizeOf, PartialEq)]
109pub(crate) struct SharedWorkerStorageKey {
110    origin: ImmutableOrigin,
111    top_level_site: Option<SharedWorkerSite>,
112}
113
114impl SharedWorkerStorageKey {
115    /// <https://storage.spec.whatwg.org/#storage-key>
116    fn for_window(global: &GlobalScope, window: &Window) -> Self {
117        // A storage key is a tuple consisting of an origin (an origin).
118        // This is expected to change; see Client-Side Storage Partitioning.
119        let origin = global.obtain_storage_key_for_non_storage_purposes();
120
121        let top_level_site = window
122            .top_level_document_if_local()
123            .map(|document| SharedWorkerSite::from_origin(document.origin().immutable().clone()));
124
125        SharedWorkerStorageKey {
126            origin,
127            top_level_site,
128        }
129    }
130}
131
132enum SharedWorkerRegistryState {
133    Creating { waiters: usize },
134    Created(SharedWorkerRegistration),
135    Failed { waiters: usize },
136}
137
138struct SharedWorkerRegistryEntry {
139    key: SharedWorkerKey,
140    state: SharedWorkerRegistryState,
141}
142
143// A `SharedWorkerGlobalScope` object has associated constructor origin (an origin), constructor URL (a URL record), and credentials (a credentials mode), and extended lifetime (a boolean).
144#[derive(Clone)]
145struct SharedWorkerRegistration {
146    id: Uuid,
147    worker_type: WorkerType,
148    credentials: CredentialsMode,
149    extended_lifetime: bool,
150    worker_is_secure_context: bool,
151    closing: Arc<AtomicBool>,
152    sender: Sender<SharedWorkerScriptMsg>,
153    _control_sender: Sender<SharedWorkerControlMsg>,
154}
155
156// A user agent has an associated shared worker manager which is the result of starting a new parallel queue.
157// Each user agent has a single shared worker manager for simplicity.
158//
159// TODO: Move this script-side approximation to a proper shared worker manager,
160// likely constellation-owned.
161static SHARED_WORKERS: LazyLock<(Mutex<Vec<SharedWorkerRegistryEntry>>, Condvar)> =
162    LazyLock::new(|| (Mutex::new(Vec::new()), Condvar::new()));
163
164// Servo-internal registry states used to serialize the SharedWorker constructor's
165// manager lookup/create work and avoid duplicate SharedWorker creation. These
166// are not spec states.
167enum SharedWorkerClaimResult {
168    Created(SharedWorkerRegistration),
169    Claimed,
170    Failed,
171}
172
173fn prune_closed_shared_workers(workers: &mut Vec<SharedWorkerRegistryEntry>) {
174    workers.retain(|entry| match &entry.state {
175        SharedWorkerRegistryState::Creating { .. } => true,
176        SharedWorkerRegistryState::Created(worker) => !worker.closing.load(Ordering::SeqCst),
177        SharedWorkerRegistryState::Failed { waiters } => *waiters > 0,
178    });
179}
180
181fn find_matching_shared_worker(
182    workers: &[SharedWorkerRegistryEntry],
183    key: &SharedWorkerKey,
184) -> Option<usize> {
185    workers.iter().position(|entry| {
186        entry.key.matches(
187            &key.storage_key,
188            &key.constructor_origin,
189            &key.constructor_url,
190            &key.name,
191        )
192    })
193}
194
195/// <https://html.spec.whatwg.org/multipage/#dom-sharedworker>
196/// <https://html.spec.whatwg.org/multipage/#shared-worker-manager>
197fn find_or_claim_shared_worker(key: SharedWorkerKey) -> SharedWorkerClaimResult {
198    let (workers, ready) = &*SHARED_WORKERS;
199    let mut workers = workers.lock().expect("SharedWorker registry poisoned");
200
201    prune_closed_shared_workers(&mut workers);
202
203    let Some(index) = find_matching_shared_worker(&workers, &key) else {
204        workers.push(SharedWorkerRegistryEntry {
205            key,
206            state: SharedWorkerRegistryState::Creating { waiters: 0 },
207        });
208        return SharedWorkerClaimResult::Claimed;
209    };
210
211    match &mut workers[index].state {
212        SharedWorkerRegistryState::Creating { waiters } => *waiters += 1,
213        SharedWorkerRegistryState::Created(registration) => {
214            return SharedWorkerClaimResult::Created(registration.clone());
215        },
216        SharedWorkerRegistryState::Failed { waiters } => *waiters += 1,
217    };
218
219    loop {
220        workers = ready.wait(workers).expect("SharedWorker registry poisoned");
221
222        let Some(index) = find_matching_shared_worker(&workers, &key) else {
223            return SharedWorkerClaimResult::Failed;
224        };
225
226        match &mut workers[index].state {
227            SharedWorkerRegistryState::Creating { .. } => {},
228            SharedWorkerRegistryState::Created(registration) => {
229                return SharedWorkerClaimResult::Created(registration.clone());
230            },
231            SharedWorkerRegistryState::Failed { waiters } => {
232                debug_assert!(*waiters > 0);
233                if *waiters > 0 {
234                    *waiters -= 1;
235                }
236                if *waiters == 0 {
237                    workers.remove(index);
238                    // No notify needed here: this waiter has already observed
239                    // the failure and no waiter remains blocked on the condvar.
240                }
241                return SharedWorkerClaimResult::Failed;
242            },
243        }
244    }
245}
246
247fn transition_creating_to_created(
248    key: &SharedWorkerKey,
249    registration: SharedWorkerRegistration,
250) -> bool {
251    let (workers, ready) = &*SHARED_WORKERS;
252    let mut workers = workers.lock().expect("SharedWorker registry poisoned");
253    let index = find_matching_shared_worker(&workers, key);
254    debug_assert!(index.is_some(), "claimed SharedWorker entry should exist");
255    let Some(index) = index else {
256        ready.notify_all();
257        return false;
258    };
259
260    let entry_is_creating = matches!(
261        &workers[index].state,
262        SharedWorkerRegistryState::Creating { .. }
263    );
264    debug_assert!(
265        entry_is_creating,
266        "claimed SharedWorker entry should still be creating"
267    );
268    if !entry_is_creating {
269        ready.notify_all();
270        return false;
271    }
272
273    workers[index].state = SharedWorkerRegistryState::Created(registration);
274    ready.notify_all();
275    true
276}
277
278fn remove_creating_shared_worker(key: &SharedWorkerKey) {
279    let (workers, ready) = &*SHARED_WORKERS;
280    let mut workers = workers.lock().expect("SharedWorker registry poisoned");
281    let index = find_matching_shared_worker(&workers, key);
282    debug_assert!(index.is_some(), "claimed SharedWorker entry should exist");
283    let Some(index) = index else {
284        return;
285    };
286
287    let waiters = match &workers[index].state {
288        SharedWorkerRegistryState::Creating { waiters } => *waiters,
289        state => {
290            debug_assert!(
291                matches!(state, SharedWorkerRegistryState::Creating { .. }),
292                "claimed SharedWorker entry should still be creating"
293            );
294            return;
295        },
296    };
297
298    if waiters == 0 {
299        workers.remove(index);
300    } else {
301        workers[index].state = SharedWorkerRegistryState::Failed { waiters };
302    };
303    ready.notify_all();
304}
305
306fn send_connect_to_created_worker(
307    registration: &SharedWorkerRegistration,
308    inside_port: MessagePortImpl,
309) -> bool {
310    registration
311        .sender
312        .send(SharedWorkerScriptMsg::Connect(inside_port))
313        .is_err()
314}
315
316impl SharedWorker {
317    pub(crate) fn unregister_shared_worker(id: Uuid) {
318        let (workers, ready) = &*SHARED_WORKERS;
319        let mut workers = workers.lock().expect("SharedWorker registry poisoned");
320        let old_len = workers.len();
321        workers.retain(|entry| {
322            !matches!(&entry.state, SharedWorkerRegistryState::Created(worker) if worker.id == id)
323        });
324        if workers.len() != old_len {
325            ready.notify_all();
326        }
327    }
328
329    fn new_inherited(
330        port: &MessagePort,
331        control_sender: Sender<SharedWorkerControlMsg>,
332    ) -> SharedWorker {
333        SharedWorker {
334            eventtarget: EventTarget::new_inherited(),
335            port: Dom::from_ref(port),
336            _control_sender: control_sender,
337        }
338    }
339
340    fn new(
341        global: &GlobalScope,
342        proto: Option<HandleObject>,
343        port: &MessagePort,
344        control_sender: Sender<SharedWorkerControlMsg>,
345        cx: &mut js::context::JSContext,
346    ) -> DomRoot<SharedWorker> {
347        reflect_dom_object_with_proto_and_cx(
348            Box::new(SharedWorker::new_inherited(port, control_sender)),
349            global,
350            proto,
351            cx,
352        )
353    }
354
355    pub(crate) fn dispatch_simple_error(cx: &mut JSContext, address: TrustedSharedWorkerAddress) {
356        let worker = address.root();
357        worker.upcast().fire_event(cx, atom!("error"));
358    }
359
360    fn queue_simple_error(global: &GlobalScope, address: TrustedSharedWorkerAddress) {
361        global.task_manager().dom_manipulation_task_source().queue(
362            task!(sharedworker_constructor_error: move |cx| {
363                SharedWorker::dispatch_simple_error(cx, address);
364            }),
365        );
366    }
367
368    fn create_entangled_inside_port(
369        cx: &mut JSContext,
370        global: &GlobalScope,
371        outside_port: &MessagePort,
372    ) -> Fallible<MessagePortImpl> {
373        let inside_port = MessagePort::new(cx, global);
374        global.track_message_port(&inside_port, None);
375        global.entangle_ports(
376            *outside_port.message_port_id(),
377            *inside_port.message_port_id(),
378        );
379        let (_, inside_port_impl) = inside_port.transfer(cx)?;
380        Ok(inside_port_impl)
381    }
382
383    /// Step 11 of onComplete of <https://html.spec.whatwg.org/multipage/#run-a-worker>
384    pub(crate) fn enable_outside_port_message_queue(
385        address: TrustedSharedWorkerAddress,
386        cx: &mut JSContext,
387    ) {
388        let worker = address.root();
389        let global = worker.global();
390        // Enable outside port's port message queue.
391        global.start_message_port(cx, worker.port.message_port_id());
392    }
393}
394
395impl SharedWorkerMethods<crate::DomTypeHolder> for SharedWorker {
396    /// <https://html.spec.whatwg.org/multipage/#dom-sharedworker>
397    fn Constructor(
398        cx: &mut JSContext,
399        window: &Window,
400        proto: Option<HandleObject>,
401        script_url: TrustedScriptURLOrUSVString,
402        options: StringOrSharedWorkerOptions,
403    ) -> Fallible<DomRoot<SharedWorker>> {
404        let global = window.upcast::<GlobalScope>();
405
406        // Step 1. Let compliantScriptURL be the result of invoking the get trusted type
407        // compliant string algorithm with TrustedScriptURL, this's relevant global object,
408        // scriptURL, "SharedWorker constructor", and "script".
409        let compliant_script_url = TrustedScriptURL::get_trusted_type_compliant_string(
410            cx,
411            global,
412            script_url,
413            "SharedWorker constructor",
414        )?;
415
416        // Step 2. If options is a DOMString, set options to a new WorkerOptions
417        // dictionary whose name member is set to the value of options and whose other
418        // members are set to their default values.
419        let worker_options = match options {
420            StringOrSharedWorkerOptions::String(name) => {
421                let mut options = SharedWorkerOptions::empty();
422                options.parent.name = name;
423                options
424            },
425            StringOrSharedWorkerOptions::SharedWorkerOptions(options) => options,
426        };
427        let worker_name = worker_options.parent.name.clone();
428        let worker_type = worker_options.parent.type_;
429        let credentials = worker_options.parent.credentials.convert();
430        let extended_lifetime = worker_options.extendedLifetime;
431
432        // Step 3. Let outsideSettings be this's relevant settings object.
433
434        // Step 4. Let urlRecord be the result of encoding-parsing a URL given
435        // compliantScriptURL, relative to outsideSettings.
436        // Step 5. If urlRecord is failure, then throw a "SyntaxError" DOMException.
437        let Ok(worker_url) = global
438            .encoding_parse_a_url(&compliant_script_url.str())
439            .map(|url| ensure_blob_referenced_by_url_is_kept_alive(global, url))
440        else {
441            return Err(Error::Syntax(None));
442        };
443        let constructor_origin = global.origin().immutable().clone();
444        let constructor_url = worker_url.url();
445
446        // Step 6. Let outsidePort be a new MessagePort in outsideSettings's realm.
447        let outside_port = MessagePort::new(cx, global);
448        global.track_message_port(&outside_port, None);
449
450        // Step 7. Set this's port to outsidePort.
451        // Step 8. Let callerIsSecureContext be true if outsideSettings is a secure
452        // context; otherwise, false.
453        let caller_is_secure_context = global.is_secure_context();
454        // Step 9. Let outsideStorageKey be the result of running obtain a storage
455        // key for non-storage purposes given outsideSettings.
456        let outside_storage_key = SharedWorkerStorageKey::for_window(global, window);
457
458        let worker_name_string = worker_name.to_string();
459        let (control_sender, control_receiver) = unbounded();
460
461        // Step 10. Let worker be this.
462        let worker = SharedWorker::new(global, proto, &outside_port, control_sender.clone(), cx);
463        let worker_addr = Trusted::new(&*worker);
464
465        // Step 11. Enqueue the following steps to the shared worker manager:
466        // Step 11.1. Let workerGlobalScope be null.
467        let shared_worker_key = SharedWorkerKey {
468            storage_key: outside_storage_key.clone(),
469            // Include constructor origin in the key so `data:` SharedWorkers are not reused across origins.
470            constructor_origin: constructor_origin.clone(),
471            constructor_url: constructor_url.clone(),
472            name: worker_name_string,
473        };
474
475        // Step 11.2. For each scope in the list of all `SharedWorkerGlobalScope` objects:
476        // Step 11.2.1. Let workerStorageKey be the result of running obtain a storage key for non-storage purposes given scope's relevant settings object.
477        // Step 11.2.2. If all of the following are true:
478        // workerStorageKey equals outsideStorageKey;
479        // scope's closing flag is false;
480        // scope's constructor URL equals urlRecord; and
481        // scope's name equals options["name"],
482        // Servo also atomically records a Creating entry here when no matching
483        // scope exists, so another same-key constructor cannot race into the
484        // Step 11.6 fresh-worker path.
485        let shared_worker = find_or_claim_shared_worker(shared_worker_key.clone());
486
487        match shared_worker {
488            SharedWorkerClaimResult::Created(registration) => {
489                // Step 11.2.2.1. Set workerGlobalScope to scope.
490                // Step 11.2.2.2. Break.
491                // TODO Step 11.3. If workerGlobalScope is not null, but the user agent has been configured to disallow communication between the worker represented by the workerGlobalScope and the scripts whose settings object is outsideSettings, then set workerGlobalScope to null.
492                // Step 11.4. If workerGlobalScope is not null, and any of the following are true:
493                // workerGlobalScope's type is not equal to options["type"];
494                // workerGlobalScope's credentials is not equal to options["credentials"]; or
495                // workerGlobalScope's extended lifetime is not equal to options["extendedLifetime"],
496                if registration.worker_type != worker_type ||
497                    registration.credentials != credentials ||
498                    registration.extended_lifetime != extended_lifetime
499                {
500                    // Step 11.4.1. Queue a global task on the DOM manipulation task source given worker's relevant global object to fire an event named error at worker.
501                    SharedWorker::queue_simple_error(global, worker_addr);
502                    // Step 11.4.2. Abort these steps.
503                    return Ok(worker);
504                }
505
506                // Step 11.5. If workerGlobalScope is not null:
507                // Step 11.5.1. Let insideSettings be workerGlobalScope's relevant settings object.
508                // Step 11.5.2. Let workerIsSecureContext be true if insideSettings is a secure context; otherwise, false.
509                // Step 11.5.3. If workerIsSecureContext is not callerIsSecureContext:
510                if registration.worker_is_secure_context != caller_is_secure_context {
511                    // Step 11.5.3.1. Queue a global task on the DOM manipulation task source given worker's relevant global object to fire an event named error at worker.
512                    SharedWorker::queue_simple_error(global, worker_addr);
513                    // Step 11.5.3.2. Abort these steps.
514                    return Ok(worker);
515                }
516
517                // Step 11.5.4. Associate worker with workerGlobalScope.
518                // Step 11.5.5. Let insidePort be a new MessagePort in insideSettings's realm.
519                // Step 11.5.6. Entangle outsidePort and insidePort.
520                let inside_port_impl =
521                    SharedWorker::create_entangled_inside_port(cx, global, &outside_port)?;
522                // Step 11.5.7. Queue a global task on the DOM manipulation task source given workerGlobalScope to fire an event named connect at workerGlobalScope, using MessageEvent, with the data attribute initialized to the empty string, the ports attribute initialized to a new frozen array containing only insidePort, and the source attribute initialized to insidePort.
523                if send_connect_to_created_worker(&registration, inside_port_impl) {
524                    SharedWorker::queue_simple_error(global, worker_addr);
525                }
526                // TODO Step 11.5.8. Append the relevant owner to add given outsideSettings to workerGlobalScope's owner set.
527                return Ok(worker);
528            },
529            SharedWorkerClaimResult::Failed => {
530                SharedWorker::queue_simple_error(global, worker_addr);
531                return Ok(worker);
532            },
533            SharedWorkerClaimResult::Claimed => {},
534        }
535
536        let initial_inside_port_impl =
537            match SharedWorker::create_entangled_inside_port(cx, global, &outside_port) {
538                Ok(inside_port_impl) => inside_port_impl,
539                Err(error) => {
540                    remove_creating_shared_worker(&shared_worker_key);
541                    return Err(error);
542                },
543            };
544
545        let parent_event_loop_sender = global
546            .event_loop_sender()
547            .expect("Window global must have an event loop sender");
548
549        let (sender, receiver) = unbounded();
550        let closing = Arc::new(AtomicBool::new(false));
551        let registration_id = Uuid::new_v4();
552
553        let worker_load_origin = WorkerScriptLoadOrigin {
554            referrer_url: match global.get_referrer() {
555                Referrer::Client(url) => Some(url),
556                Referrer::ReferrerUrl(url) => Some(url),
557                _ => None,
558            },
559            referrer_policy: global.get_referrer_policy(),
560            pipeline_id: global.pipeline_id(),
561        };
562
563        let (devtools_sender, devtools_receiver) = generic_channel::channel().unwrap();
564        let worker_id = WorkerId(Uuid::new_v4());
565        if let Some(chan) = global.devtools_chan() {
566            let webview_id = global
567                .webview_id()
568                .expect("Window global must have a WebViewId");
569            let page_info = DevtoolsPageInfo {
570                title: format!("SharedWorker for {}", worker_url.url()),
571                url: worker_url.url(),
572                is_top_level_global: false,
573                is_service_worker: false,
574            };
575            let _ = chan.send(ScriptToDevtoolsControlMsg::NewGlobal(
576                (
577                    window.window_proxy().browsing_context_id(),
578                    global.pipeline_id(),
579                    Some(worker_id),
580                    webview_id,
581                ),
582                devtools_sender.clone(),
583                page_info,
584            ));
585        }
586
587        let init = prepare_workerscope_init(
588            global,
589            Some(devtools_sender),
590            Some(worker_id),
591            window.webgl_chan_value(),
592        );
593
594        let (setup_sender, setup_receiver) = unbounded();
595        let (registered_sender, registered_receiver) = unbounded();
596
597        // Step 11.6. Otherwise, in parallel, run a worker given worker, urlRecord, outsideSettings, outsidePort, and options.
598        let _join_handle = match SharedWorkerGlobalScope::run_shared_worker_scope(
599            init,
600            window.webview_id(),
601            Some(window.window_proxy().browsing_context_id()),
602            worker_name,
603            worker_type,
604            worker_url,
605            worker_addr.clone(),
606            parent_event_loop_sender,
607            devtools_receiver,
608            sender.clone(),
609            receiver,
610            worker_load_origin,
611            closing.clone(),
612            global.image_cache(),
613            #[cfg(feature = "webgpu")]
614            global.wgpu_id_hub(),
615            control_receiver,
616            setup_sender,
617            registered_receiver,
618            registration_id,
619            credentials,
620            extended_lifetime,
621            constructor_origin,
622            constructor_url,
623            outside_storage_key,
624            global.insecure_requests_policy(),
625            global.policy_container(),
626            global.font_context().cloned(),
627        ) {
628            Ok(join_handle) => join_handle,
629            Err(error) => {
630                error!("Failed to spawn SharedWorker thread: {error}");
631                remove_creating_shared_worker(&shared_worker_key);
632                SharedWorker::queue_simple_error(global, worker_addr);
633                return Ok(worker);
634            },
635        };
636
637        // Step 11.5.2. Let workerIsSecureContext be true if insideSettings is a secure context; otherwise, false.
638        let Ok(worker_is_secure_context) = setup_receiver.recv() else {
639            remove_creating_shared_worker(&shared_worker_key);
640            SharedWorker::queue_simple_error(global, worker_addr);
641            return Ok(worker);
642        };
643
644        let registration = SharedWorkerRegistration {
645            id: registration_id,
646            worker_type,
647            credentials,
648            extended_lifetime,
649            worker_is_secure_context,
650            closing,
651            sender,
652            _control_sender: control_sender,
653        };
654
655        if !transition_creating_to_created(&shared_worker_key, registration.clone()) {
656            SharedWorker::queue_simple_error(global, worker_addr);
657            return Ok(worker);
658        }
659
660        if registered_sender.send(()).is_err() {
661            SharedWorker::unregister_shared_worker(registration_id);
662            SharedWorker::queue_simple_error(global, worker_addr);
663            return Ok(worker);
664        }
665
666        // Step 13. If is shared is true, then queue a global task on the DOM
667        // manipulation task source given worker global scope to fire an event
668        // named connect at worker global scope, using MessageEvent, with the data
669        // attribute initialized to the empty string, the ports attribute
670        // initialized to a new frozen array containing inside port, and the
671        // source attribute initialized to inside port.
672        if send_connect_to_created_worker(&registration, initial_inside_port_impl) {
673            SharedWorker::unregister_shared_worker(registration_id);
674            SharedWorker::queue_simple_error(global, worker_addr);
675            return Ok(worker);
676        }
677
678        Ok(worker)
679    }
680
681    /// <https://html.spec.whatwg.org/multipage/#dom-sharedworker-port>
682    fn Port(&self) -> DomRoot<MessagePort> {
683        // The port getter steps are to return this's port.
684        DomRoot::from_ref(&*self.port)
685    }
686
687    // <https://html.spec.whatwg.org/multipage/#handler-abstractworker-onerror>
688    event_handler!(error, GetOnerror, SetOnerror);
689}
690
691impl TaskOnce for SimpleWorkerErrorHandler<SharedWorker> {
692    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
693    fn run_once(self, cx: &mut JSContext) {
694        SharedWorker::dispatch_simple_error(cx, self.addr);
695    }
696}