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