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    /// Set online status for all living shared workers.
399    pub(crate) fn set_network_online_state(online: bool) {
400        let (workers, _ready) = &*SHARED_WORKERS;
401        let workers = workers.lock().expect("SharedWorker registry poisoned");
402        for entry in workers.iter() {
403            let SharedWorkerRegistryState::Created(registration) = &entry.state else {
404                continue;
405            };
406            if registration.closing.load(Ordering::SeqCst) {
407                continue;
408            }
409            let _ = registration
410                ._control_sender
411                .send(SharedWorkerControlMsg::SetNetworkOnlineState(online));
412        }
413    }
414}
415
416impl SharedWorkerMethods<crate::DomTypeHolder> for SharedWorker {
417    /// <https://html.spec.whatwg.org/multipage/#dom-sharedworker>
418    fn Constructor(
419        cx: &mut JSContext,
420        window: &Window,
421        proto: Option<HandleObject>,
422        script_url: TrustedScriptURLOrUSVString,
423        options: StringOrSharedWorkerOptions,
424    ) -> Fallible<DomRoot<SharedWorker>> {
425        let global = window.upcast::<GlobalScope>();
426
427        // Step 1. Let compliantScriptURL be the result of invoking the get trusted type
428        // compliant string algorithm with TrustedScriptURL, this's relevant global object,
429        // scriptURL, "SharedWorker constructor", and "script".
430        let compliant_script_url = TrustedScriptURL::get_trusted_type_compliant_string(
431            cx,
432            global,
433            script_url,
434            "SharedWorker constructor",
435        )?;
436
437        // Step 2. If options is a DOMString, set options to a new WorkerOptions
438        // dictionary whose name member is set to the value of options and whose other
439        // members are set to their default values.
440        let worker_options = match options {
441            StringOrSharedWorkerOptions::String(name) => {
442                let mut options = SharedWorkerOptions::empty();
443                options.parent.name = name;
444                options
445            },
446            StringOrSharedWorkerOptions::SharedWorkerOptions(options) => options,
447        };
448        let worker_name = worker_options.parent.name.clone();
449        let worker_type = worker_options.parent.type_;
450        let credentials = worker_options.parent.credentials.convert();
451        let extended_lifetime = worker_options.extendedLifetime;
452
453        // Step 3. Let outsideSettings be this's relevant settings object.
454
455        // Step 4. Let urlRecord be the result of encoding-parsing a URL given
456        // compliantScriptURL, relative to outsideSettings.
457        // Step 5. If urlRecord is failure, then throw a "SyntaxError" DOMException.
458        let Ok(worker_url) = global
459            .encoding_parse_a_url(&compliant_script_url.str())
460            .map(|url| ensure_blob_referenced_by_url_is_kept_alive(global, url))
461        else {
462            return Err(Error::Syntax(None));
463        };
464        let constructor_origin = global.origin().immutable().clone();
465        let constructor_url = worker_url.url();
466
467        // Step 6. Let outsidePort be a new MessagePort in outsideSettings's realm.
468        let outside_port = MessagePort::new(cx, global);
469        global.track_message_port(&outside_port, None);
470
471        // Step 7. Set this's port to outsidePort.
472        // Step 8. Let callerIsSecureContext be true if outsideSettings is a secure
473        // context; otherwise, false.
474        let caller_is_secure_context = global.is_secure_context();
475        // Step 9. Let outsideStorageKey be the result of running obtain a storage
476        // key for non-storage purposes given outsideSettings.
477        let outside_storage_key = SharedWorkerStorageKey::for_window(global, window);
478
479        let worker_name_string = worker_name.to_string();
480        let (control_sender, control_receiver) = unbounded();
481
482        // Step 10. Let worker be this.
483        let worker = SharedWorker::new(global, proto, &outside_port, control_sender.clone(), cx);
484        let worker_addr = Trusted::new(&*worker);
485
486        // Step 11. Enqueue the following steps to the shared worker manager:
487        // Step 11.1. Let workerGlobalScope be null.
488        let shared_worker_key = SharedWorkerKey {
489            storage_key: outside_storage_key.clone(),
490            // Include constructor origin in the key so `data:` SharedWorkers are not reused across origins.
491            constructor_origin: constructor_origin.clone(),
492            constructor_url: constructor_url.clone(),
493            name: worker_name_string,
494        };
495
496        // Step 11.2. For each scope in the list of all `SharedWorkerGlobalScope` objects:
497        // 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.
498        // Step 11.2.2. If all of the following are true:
499        // workerStorageKey equals outsideStorageKey;
500        // scope's closing flag is false;
501        // scope's constructor URL equals urlRecord; and
502        // scope's name equals options["name"],
503        // Servo also atomically records a Creating entry here when no matching
504        // scope exists, so another same-key constructor cannot race into the
505        // Step 11.6 fresh-worker path.
506        let shared_worker = find_or_claim_shared_worker(shared_worker_key.clone());
507
508        match shared_worker {
509            SharedWorkerClaimResult::Created(registration) => {
510                // Step 11.2.2.1. Set workerGlobalScope to scope.
511                // Step 11.2.2.2. Break.
512                // 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.
513                // Step 11.4. If workerGlobalScope is not null, and any of the following are true:
514                // workerGlobalScope's type is not equal to options["type"];
515                // workerGlobalScope's credentials is not equal to options["credentials"]; or
516                // workerGlobalScope's extended lifetime is not equal to options["extendedLifetime"],
517                if registration.worker_type != worker_type ||
518                    registration.credentials != credentials ||
519                    registration.extended_lifetime != extended_lifetime
520                {
521                    // 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.
522                    SharedWorker::queue_simple_error(global, worker_addr);
523                    // Step 11.4.2. Abort these steps.
524                    return Ok(worker);
525                }
526
527                // Step 11.5. If workerGlobalScope is not null:
528                // Step 11.5.1. Let insideSettings be workerGlobalScope's relevant settings object.
529                // Step 11.5.2. Let workerIsSecureContext be true if insideSettings is a secure context; otherwise, false.
530                // Step 11.5.3. If workerIsSecureContext is not callerIsSecureContext:
531                if registration.worker_is_secure_context != caller_is_secure_context {
532                    // 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.
533                    SharedWorker::queue_simple_error(global, worker_addr);
534                    // Step 11.5.3.2. Abort these steps.
535                    return Ok(worker);
536                }
537
538                // Step 11.5.4. Associate worker with workerGlobalScope.
539                // Step 11.5.5. Let insidePort be a new MessagePort in insideSettings's realm.
540                // Step 11.5.6. Entangle outsidePort and insidePort.
541                let inside_port_impl =
542                    SharedWorker::create_entangled_inside_port(cx, global, &outside_port)?;
543                // 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.
544                if send_connect_to_created_worker(&registration, inside_port_impl) {
545                    SharedWorker::queue_simple_error(global, worker_addr);
546                }
547                // TODO Step 11.5.8. Append the relevant owner to add given outsideSettings to workerGlobalScope's owner set.
548                return Ok(worker);
549            },
550            SharedWorkerClaimResult::Failed => {
551                SharedWorker::queue_simple_error(global, worker_addr);
552                return Ok(worker);
553            },
554            SharedWorkerClaimResult::Claimed => {},
555        }
556
557        let initial_inside_port_impl =
558            match SharedWorker::create_entangled_inside_port(cx, global, &outside_port) {
559                Ok(inside_port_impl) => inside_port_impl,
560                Err(error) => {
561                    remove_creating_shared_worker(&shared_worker_key);
562                    return Err(error);
563                },
564            };
565
566        let parent_event_loop_sender = global
567            .event_loop_sender()
568            .expect("Window global must have an event loop sender");
569
570        let (sender, receiver) = unbounded();
571        let closing = Arc::new(AtomicBool::new(false));
572        let registration_id = Uuid::new_v4();
573
574        let worker_load_origin = WorkerScriptLoadOrigin {
575            referrer_url: match global.get_referrer() {
576                Referrer::Client(url) => Some(url),
577                Referrer::ReferrerUrl(url) => Some(url),
578                _ => None,
579            },
580            referrer_policy: global.get_referrer_policy(),
581            pipeline_id: global.pipeline_id(),
582        };
583
584        #[cfg(feature = "devtools")]
585        let ((devtools_sender, devtools_receiver), worker_id) = {
586            let (devtools_sender, devtools_receiver) = generic_channel::channel().unwrap();
587            let worker_id = Some(WorkerId(Uuid::new_v4()));
588
589            if let Some(chan) = global.devtools_chan() {
590                let webview_id = global
591                    .webview_id()
592                    .expect("Window global must have a WebViewId");
593                let page_info = DevtoolsPageInfo {
594                    title: format!("SharedWorker for {}", worker_url.url()),
595                    url: worker_url.url(),
596                    is_top_level_global: false,
597                    is_service_worker: false,
598                };
599                let _ = chan.send(ScriptToDevtoolsControlMsg::NewGlobal(
600                    (
601                        window.window_proxy().browsing_context_id(),
602                        global.pipeline_id(),
603                        worker_id,
604                        webview_id,
605                    ),
606                    devtools_sender.clone(),
607                    page_info,
608                ));
609            }
610
611            ((devtools_sender, devtools_receiver), worker_id)
612        };
613        #[cfg(not(feature = "devtools"))]
614        let worker_id: Option<WorkerId> = None;
615
616        let init = prepare_workerscope_init(
617            global,
618            #[cfg(feature = "devtools")]
619            Some(devtools_sender),
620            worker_id,
621            #[cfg(feature = "webgl")]
622            window.webgl_chan(),
623        );
624
625        let (setup_sender, setup_receiver) = unbounded();
626        let (registered_sender, registered_receiver) = unbounded();
627
628        // Step 11.6. Otherwise, in parallel, run a worker given worker, urlRecord, outsideSettings, outsidePort, and options.
629        let _join_handle = match SharedWorkerGlobalScope::run_shared_worker_scope(
630            init,
631            window.webview_id(),
632            Some(window.window_proxy().browsing_context_id()),
633            worker_name,
634            worker_type,
635            worker_url,
636            worker_addr.clone(),
637            parent_event_loop_sender,
638            #[cfg(feature = "devtools")]
639            devtools_receiver,
640            sender.clone(),
641            receiver,
642            worker_load_origin,
643            closing.clone(),
644            global.image_cache(),
645            #[cfg(feature = "webgpu")]
646            global.wgpu_id_hub(),
647            control_receiver,
648            setup_sender,
649            registered_receiver,
650            registration_id,
651            credentials,
652            extended_lifetime,
653            constructor_origin,
654            constructor_url,
655            outside_storage_key,
656            global.insecure_requests_policy(),
657            global.policy_container(),
658            global.font_context(),
659        ) {
660            Ok(join_handle) => join_handle,
661            Err(error) => {
662                error!("Failed to spawn SharedWorker thread: {error}");
663                remove_creating_shared_worker(&shared_worker_key);
664                SharedWorker::queue_simple_error(global, worker_addr);
665                return Ok(worker);
666            },
667        };
668
669        // Step 11.5.2. Let workerIsSecureContext be true if insideSettings is a secure context; otherwise, false.
670        let Ok(worker_is_secure_context) = setup_receiver.recv() else {
671            remove_creating_shared_worker(&shared_worker_key);
672            SharedWorker::queue_simple_error(global, worker_addr);
673            return Ok(worker);
674        };
675
676        let registration = SharedWorkerRegistration {
677            id: registration_id,
678            worker_type,
679            credentials,
680            extended_lifetime,
681            worker_is_secure_context,
682            closing,
683            sender,
684            _control_sender: control_sender,
685        };
686
687        if !transition_creating_to_created(&shared_worker_key, registration.clone()) {
688            SharedWorker::queue_simple_error(global, worker_addr);
689            return Ok(worker);
690        }
691
692        if registered_sender.send(()).is_err() {
693            SharedWorker::unregister_shared_worker(registration_id);
694            SharedWorker::queue_simple_error(global, worker_addr);
695            return Ok(worker);
696        }
697
698        // Step 13. If is shared is true, then queue a global task on the DOM
699        // manipulation task source given worker global scope to fire an event
700        // named connect at worker global scope, using MessageEvent, with the data
701        // attribute initialized to the empty string, the ports attribute
702        // initialized to a new frozen array containing inside port, and the
703        // source attribute initialized to inside port.
704        if send_connect_to_created_worker(&registration, initial_inside_port_impl) {
705            SharedWorker::unregister_shared_worker(registration_id);
706            SharedWorker::queue_simple_error(global, worker_addr);
707            return Ok(worker);
708        }
709
710        Ok(worker)
711    }
712
713    /// <https://html.spec.whatwg.org/multipage/#dom-sharedworker-port>
714    fn Port(&self) -> DomRoot<MessagePort> {
715        // The port getter steps are to return this's port.
716        DomRoot::from_ref(&*self.port)
717    }
718
719    // <https://html.spec.whatwg.org/multipage/#handler-abstractworker-onerror>
720    event_handler!(error, GetOnerror, SetOnerror);
721}
722
723impl TaskOnce for SimpleWorkerErrorHandler<SharedWorker> {
724    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
725    fn run_once(self, cx: &mut JSContext) {
726        SharedWorker::dispatch_simple_error(cx, self.addr);
727    }
728}