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