script/dom/workers/
serviceworker.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6
7use base::id::ServiceWorkerId;
8use constellation_traits::{DOMMessage, ScriptToConstellationMessage};
9use dom_struct::dom_struct;
10use js::context::JSContext;
11use js::jsapi::{Heap, JSObject};
12use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue};
13use servo_url::ServoUrl;
14
15use crate::dom::abstractworker::SimpleWorkerErrorHandler;
16use crate::dom::bindings::cell::DomRefCell;
17use crate::dom::bindings::codegen::Bindings::MessagePortBinding::StructuredSerializeOptions;
18use crate::dom::bindings::codegen::Bindings::ServiceWorkerBinding::{
19    ServiceWorkerMethods, ServiceWorkerState,
20};
21use crate::dom::bindings::error::{Error, ErrorResult};
22use crate::dom::bindings::inheritance::Castable;
23use crate::dom::bindings::refcounted::Trusted;
24use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
25use crate::dom::bindings::root::DomRoot;
26use crate::dom::bindings::str::USVString;
27use crate::dom::bindings::structuredclone;
28use crate::dom::bindings::trace::RootedTraceableBox;
29use crate::dom::eventtarget::EventTarget;
30use crate::dom::globalscope::GlobalScope;
31use crate::script_runtime::CanGc;
32use crate::task::TaskOnce;
33
34pub(crate) type TrustedServiceWorkerAddress = Trusted<ServiceWorker>;
35
36#[dom_struct]
37pub(crate) struct ServiceWorker {
38    eventtarget: EventTarget,
39    script_url: DomRefCell<String>,
40    #[no_trace]
41    scope_url: ServoUrl,
42    state: Cell<ServiceWorkerState>,
43    #[no_trace]
44    worker_id: ServiceWorkerId,
45}
46
47impl ServiceWorker {
48    fn new_inherited(
49        script_url: &str,
50        scope_url: ServoUrl,
51        worker_id: ServiceWorkerId,
52    ) -> ServiceWorker {
53        ServiceWorker {
54            eventtarget: EventTarget::new_inherited(),
55            script_url: DomRefCell::new(String::from(script_url)),
56            state: Cell::new(ServiceWorkerState::Installing),
57            scope_url,
58            worker_id,
59        }
60    }
61
62    pub(crate) fn new(
63        global: &GlobalScope,
64        script_url: ServoUrl,
65        scope_url: ServoUrl,
66        worker_id: ServiceWorkerId,
67        can_gc: CanGc,
68    ) -> DomRoot<ServiceWorker> {
69        reflect_dom_object(
70            Box::new(ServiceWorker::new_inherited(
71                script_url.as_str(),
72                scope_url,
73                worker_id,
74            )),
75            global,
76            can_gc,
77        )
78    }
79
80    pub(crate) fn dispatch_simple_error(address: TrustedServiceWorkerAddress, can_gc: CanGc) {
81        let service_worker = address.root();
82        service_worker.upcast().fire_event(atom!("error"), can_gc);
83    }
84
85    pub(crate) fn set_transition_state(&self, state: ServiceWorkerState, can_gc: CanGc) {
86        self.state.set(state);
87        self.upcast::<EventTarget>()
88            .fire_event(atom!("statechange"), can_gc);
89    }
90
91    pub(crate) fn get_script_url(&self) -> ServoUrl {
92        ServoUrl::parse(&self.script_url.borrow().clone()).unwrap()
93    }
94
95    /// <https://w3c.github.io/ServiceWorker/#service-worker-postmessage>
96    fn post_message_impl(
97        &self,
98        cx: &mut JSContext,
99        message: HandleValue,
100        transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>,
101    ) -> ErrorResult {
102        // Step 1
103        if let ServiceWorkerState::Redundant = self.state.get() {
104            return Err(Error::InvalidState(None));
105        }
106        // Step 7
107        let data = structuredclone::write(cx.into(), message, Some(transfer))?;
108        let incumbent = GlobalScope::incumbent().expect("no incumbent global?");
109        let msg_vec = DOMMessage {
110            origin: incumbent.origin().immutable().clone(),
111            data,
112        };
113        let _ = self.global().script_to_constellation_chan().send(
114            ScriptToConstellationMessage::ForwardDOMMessage(msg_vec, self.scope_url.clone()),
115        );
116        Ok(())
117    }
118}
119
120impl ServiceWorkerMethods<crate::DomTypeHolder> for ServiceWorker {
121    /// <https://w3c.github.io/ServiceWorker/#service-worker-state-attribute>
122    fn State(&self) -> ServiceWorkerState {
123        self.state.get()
124    }
125
126    /// <https://w3c.github.io/ServiceWorker/#service-worker-url-attribute>
127    fn ScriptURL(&self) -> USVString {
128        USVString(self.script_url.borrow().clone())
129    }
130
131    /// <https://w3c.github.io/ServiceWorker/#service-worker-postmessage>
132    fn PostMessage(
133        &self,
134        cx: &mut JSContext,
135        message: HandleValue,
136        transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>,
137    ) -> ErrorResult {
138        self.post_message_impl(cx, message, transfer)
139    }
140
141    /// <https://w3c.github.io/ServiceWorker/#service-worker-postmessage>
142    fn PostMessage_(
143        &self,
144        cx: &mut JSContext,
145        message: HandleValue,
146        options: RootedTraceableBox<StructuredSerializeOptions>,
147    ) -> ErrorResult {
148        let mut rooted = CustomAutoRooter::new(
149            options
150                .transfer
151                .iter()
152                .map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get())
153                .collect(),
154        );
155        #[expect(unsafe_code)]
156        let guard = unsafe { CustomAutoRooterGuard::new(cx.raw_cx(), &mut rooted) };
157        self.post_message_impl(cx, message, guard)
158    }
159
160    // https://w3c.github.io/ServiceWorker/#service-worker-container-onerror-attribute
161    event_handler!(error, GetOnerror, SetOnerror);
162
163    // https://w3c.github.io/ServiceWorker/#ref-for-service-worker-onstatechange-attribute-1
164    event_handler!(statechange, GetOnstatechange, SetOnstatechange);
165}
166
167impl TaskOnce for SimpleWorkerErrorHandler<ServiceWorker> {
168    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
169    fn run_once(self, cx: &mut JSContext) {
170        ServiceWorker::dispatch_simple_error(self.addr, CanGc::from_cx(cx));
171    }
172}