script/dom/
messageport.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 http://mozilla.org/MPL/2.0/. */
4
5use std::cell::{Cell, RefCell};
6use std::ptr;
7use std::rc::Rc;
8
9use base::id::{MessagePortId, MessagePortIndex};
10use constellation_traits::{MessagePortImpl, PortMessageTask};
11use dom_struct::dom_struct;
12use js::context::JSContext;
13use js::jsapi::{Heap, JS_NewObject, JSObject};
14use js::jsval::UndefinedValue;
15use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue};
16use rustc_hash::FxHashMap;
17use script_bindings::conversions::SafeToJSValConvertible;
18
19use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
20use crate::dom::bindings::codegen::Bindings::MessagePortBinding::{
21    MessagePortMethods, StructuredSerializeOptions,
22};
23use crate::dom::bindings::conversions::root_from_object;
24use crate::dom::bindings::error::{Error, ErrorResult, ErrorToJsval, Fallible};
25use crate::dom::bindings::inheritance::Castable;
26use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
27use crate::dom::bindings::root::DomRoot;
28use crate::dom::bindings::structuredclone::{self, StructuredData};
29use crate::dom::bindings::trace::RootedTraceableBox;
30use crate::dom::bindings::transferable::Transferable;
31use crate::dom::bindings::utils::set_dictionary_property;
32use crate::dom::eventtarget::EventTarget;
33use crate::dom::globalscope::GlobalScope;
34use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
35
36#[dom_struct]
37/// The MessagePort used in the DOM.
38pub(crate) struct MessagePort {
39    eventtarget: EventTarget,
40    #[no_trace]
41    message_port_id: MessagePortId,
42    #[no_trace]
43    entangled_port: RefCell<Option<MessagePortId>>,
44    detached: Cell<bool>,
45}
46
47impl MessagePort {
48    fn new_inherited(message_port_id: MessagePortId) -> MessagePort {
49        MessagePort {
50            eventtarget: EventTarget::new_inherited(),
51            entangled_port: RefCell::new(None),
52            detached: Cell::new(false),
53            message_port_id,
54        }
55    }
56
57    /// <https://html.spec.whatwg.org/multipage/#create-a-new-messageport-object>
58    pub(crate) fn new(owner: &GlobalScope, can_gc: CanGc) -> DomRoot<MessagePort> {
59        let port_id = MessagePortId::new();
60        reflect_dom_object(Box::new(MessagePort::new_inherited(port_id)), owner, can_gc)
61    }
62
63    /// Create a new port for an incoming transfer-received one.
64    pub(crate) fn new_transferred(
65        owner: &GlobalScope,
66        transferred_port: MessagePortId,
67        entangled_port: Option<MessagePortId>,
68        can_gc: CanGc,
69    ) -> DomRoot<MessagePort> {
70        reflect_dom_object(
71            Box::new(MessagePort {
72                message_port_id: transferred_port,
73                eventtarget: EventTarget::new_inherited(),
74                detached: Cell::new(false),
75                entangled_port: RefCell::new(entangled_port),
76            }),
77            owner,
78            can_gc,
79        )
80    }
81
82    /// <https://html.spec.whatwg.org/multipage/#entangle>
83    pub(crate) fn entangle(&self, other_id: MessagePortId) {
84        *self.entangled_port.borrow_mut() = Some(other_id);
85    }
86
87    /// <https://html.spec.whatwg.org/multipage/#disentangle>
88    pub(crate) fn disentangle(&self) -> Option<MessagePortId> {
89        // Disentangle initiatorPort and otherPort, so that they are no longer entangled or associated with each other.
90        // Note: called from `disentangle_port` in the global, where the rest happens.
91        self.entangled_port.borrow_mut().take()
92    }
93
94    /// Has the port been disentangled?
95    /// Used when starting the port to fire the `close` event,
96    /// to cover the case of a disentanglement while in transfer.
97    pub(crate) fn disentangled(&self) -> bool {
98        self.entangled_port.borrow().is_none()
99    }
100
101    pub(crate) fn message_port_id(&self) -> &MessagePortId {
102        &self.message_port_id
103    }
104
105    pub(crate) fn detached(&self) -> bool {
106        self.detached.get()
107    }
108
109    /// <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessage>
110    fn set_onmessage(&self, listener: Option<Rc<EventHandlerNonNull>>) {
111        let eventtarget = self.upcast::<EventTarget>();
112        eventtarget.set_event_handler_common("message", listener);
113    }
114
115    /// <https://html.spec.whatwg.org/multipage/#message-port-post-message-steps>
116    #[expect(unsafe_code)]
117    fn post_message_impl(
118        &self,
119        cx: SafeJSContext,
120        message: HandleValue,
121        transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>,
122    ) -> ErrorResult {
123        if self.detached.get() {
124            return Ok(());
125        }
126
127        // Step 1 is the transfer argument.
128
129        let target_port = self.entangled_port.borrow();
130
131        // Step 3
132        let mut doomed = false;
133
134        let ports = transfer
135            .iter()
136            .filter_map(|&obj| unsafe { root_from_object::<MessagePort>(obj, cx.raw_cx()).ok() });
137        for port in ports {
138            // Step 2
139            if port.message_port_id() == self.message_port_id() {
140                return Err(Error::DataClone(None));
141            }
142
143            // Step 4
144            if let Some(target_id) = target_port.as_ref() {
145                if port.message_port_id() == target_id {
146                    doomed = true;
147                }
148            }
149        }
150
151        // Step 5
152        let data = structuredclone::write(cx, message, Some(transfer))?;
153
154        if doomed {
155            // TODO: The spec says to optionally report such a case to a dev console.
156            return Ok(());
157        }
158
159        // Step 6, done in MessagePortImpl.
160
161        let incumbent = match GlobalScope::incumbent() {
162            None => unreachable!("postMessage called with no incumbent global"),
163            Some(incumbent) => incumbent,
164        };
165
166        // Step 7
167        let task = PortMessageTask {
168            origin: incumbent.origin().immutable().clone(),
169            data,
170        };
171
172        // Have the global proxy this call to the corresponding MessagePortImpl.
173        self.global()
174            .post_messageport_msg(*self.message_port_id(), task);
175        Ok(())
176    }
177
178    /// <https://streams.spec.whatwg.org/#abstract-opdef-crossrealmtransformsenderror>
179    pub(crate) fn cross_realm_transform_send_error(&self, error: HandleValue, can_gc: CanGc) {
180        // Perform PackAndPostMessage(port, "error", error),
181        // discarding the result.
182        let _ = self.pack_and_post_message("error", error, can_gc);
183    }
184
185    /// <https://streams.spec.whatwg.org/#abstract-opdef-packandpostmessagehandlingerror>
186    pub(crate) fn pack_and_post_message_handling_error(
187        &self,
188        type_: &str,
189        value: HandleValue,
190        can_gc: CanGc,
191    ) -> ErrorResult {
192        // Let result be PackAndPostMessage(port, type, value).
193        let result = self.pack_and_post_message(type_, value, can_gc);
194
195        // If result is an abrupt completion,
196        if let Err(error) = result.as_ref() {
197            // Perform ! CrossRealmTransformSendError(port, result.[[Value]]).
198            let cx = GlobalScope::get_cx();
199            rooted!(in(*cx) let mut rooted_error = UndefinedValue());
200            error
201                .clone()
202                .to_jsval(cx, &self.global(), rooted_error.handle_mut(), can_gc);
203            self.cross_realm_transform_send_error(rooted_error.handle(), can_gc);
204        }
205
206        result
207    }
208
209    /// <https://streams.spec.whatwg.org/#abstract-opdef-packandpostmessage>
210    #[expect(unsafe_code)]
211    pub(crate) fn pack_and_post_message(
212        &self,
213        type_: &str,
214        value: HandleValue,
215        can_gc: CanGc,
216    ) -> ErrorResult {
217        let cx = GlobalScope::get_cx();
218
219        // Let message be OrdinaryObjectCreate(null).
220        rooted!(in(*cx) let mut message = unsafe { JS_NewObject(*cx, ptr::null()) });
221        rooted!(in(*cx) let mut type_string = UndefinedValue());
222        type_.safe_to_jsval(cx, type_string.handle_mut(), can_gc);
223
224        // Perform ! CreateDataProperty(message, "type", type).
225        set_dictionary_property(cx, message.handle(), c"type", type_string.handle())
226            .expect("Setting the message type should not fail.");
227
228        // Perform ! CreateDataProperty(message, "value", value).
229        set_dictionary_property(cx, message.handle(), c"value", value)
230            .expect("Setting the message value should not fail.");
231
232        // Let targetPort be the port with which port is entangled, if any; otherwise let it be null.
233        // Done in `global.post_messageport_msg`.
234
235        // Let options be «[ "transfer" → « » ]».
236        let mut rooted = CustomAutoRooter::new(vec![]);
237        let transfer = CustomAutoRooterGuard::new(*cx, &mut rooted);
238
239        // Run the message port post message steps providing targetPort, message, and options.
240        rooted!(in(*cx) let mut message_val = UndefinedValue());
241        message.safe_to_jsval(cx, message_val.handle_mut(), can_gc);
242        self.post_message_impl(cx, message_val.handle(), transfer)
243    }
244}
245
246impl Transferable for MessagePort {
247    type Index = MessagePortIndex;
248    type Data = MessagePortImpl;
249
250    /// <https://html.spec.whatwg.org/multipage/#message-ports:transfer-steps>
251    fn transfer(
252        &self,
253        _cx: &mut js::context::JSContext,
254    ) -> Fallible<(MessagePortId, MessagePortImpl)> {
255        // <https://html.spec.whatwg.org/multipage/#structuredserializewithtransfer>
256        // Step 5.2. If transferable has a [[Detached]] internal slot and
257        // transferable.[[Detached]] is true, then throw a "DataCloneError"
258        // DOMException.
259        if self.detached.get() {
260            return Err(Error::DataClone(None));
261        }
262
263        self.detached.set(true);
264        let id = self.message_port_id();
265
266        // 1. Run local transfer logic, and return the object to be transferred.
267        let transferred_port = self.global().mark_port_as_transferred(id);
268
269        Ok((*id, transferred_port))
270    }
271
272    /// <https://html.spec.whatwg.org/multipage/#message-ports:transfer-receiving-steps>
273    fn transfer_receive(
274        cx: &mut js::context::JSContext,
275        owner: &GlobalScope,
276        id: MessagePortId,
277        port_impl: MessagePortImpl,
278    ) -> Result<DomRoot<Self>, ()> {
279        let transferred_port = MessagePort::new_transferred(
280            owner,
281            id,
282            port_impl.entangled_port_id(),
283            CanGc::from_cx(cx),
284        );
285        owner.track_message_port(&transferred_port, Some(port_impl));
286        Ok(transferred_port)
287    }
288
289    fn serialized_storage<'a>(
290        data: StructuredData<'a, '_>,
291    ) -> &'a mut Option<FxHashMap<MessagePortId, Self::Data>> {
292        match data {
293            StructuredData::Reader(r) => &mut r.port_impls,
294            StructuredData::Writer(w) => &mut w.ports,
295        }
296    }
297}
298
299impl MessagePortMethods<crate::DomTypeHolder> for MessagePort {
300    /// <https://html.spec.whatwg.org/multipage/#dom-messageport-postmessage>
301    fn PostMessage(
302        &self,
303        cx: &mut JSContext,
304        message: HandleValue,
305        transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>,
306    ) -> ErrorResult {
307        if self.detached.get() {
308            return Ok(());
309        }
310        self.post_message_impl(cx.into(), message, transfer)
311    }
312
313    /// <https://html.spec.whatwg.org/multipage/#dom-messageport-postmessage>
314    fn PostMessage_(
315        &self,
316        cx: &mut JSContext,
317        message: HandleValue,
318        options: RootedTraceableBox<StructuredSerializeOptions>,
319    ) -> ErrorResult {
320        if self.detached.get() {
321            return Ok(());
322        }
323        let mut rooted = CustomAutoRooter::new(
324            options
325                .transfer
326                .iter()
327                .map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get())
328                .collect(),
329        );
330        #[expect(unsafe_code)]
331        let guard = unsafe { CustomAutoRooterGuard::new(cx.raw_cx(), &mut rooted) };
332        self.post_message_impl(cx.into(), message, guard)
333    }
334
335    /// <https://html.spec.whatwg.org/multipage/#dom-messageport-start>
336    fn Start(&self, can_gc: CanGc) {
337        if self.detached.get() {
338            return;
339        }
340        self.global()
341            .start_message_port(self.message_port_id(), can_gc);
342    }
343
344    /// <https://html.spec.whatwg.org/multipage/#dom-messageport-close>
345    fn Close(&self, can_gc: CanGc) {
346        // Set this's [[Detached]] internal slot value to true.
347        self.detached.set(true);
348
349        let global = self.global();
350        global.close_message_port(self.message_port_id());
351
352        // If this is entangled, disentangle it.
353        global.disentangle_port(self, can_gc);
354    }
355
356    /// <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessage>
357    fn GetOnmessage(&self, can_gc: CanGc) -> Option<Rc<EventHandlerNonNull>> {
358        if self.detached.get() {
359            return None;
360        }
361        let eventtarget = self.upcast::<EventTarget>();
362        eventtarget.get_event_handler_common("message", can_gc)
363    }
364
365    /// <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessage>
366    fn SetOnmessage(&self, listener: Option<Rc<EventHandlerNonNull>>, can_gc: CanGc) {
367        if self.detached.get() {
368            return;
369        }
370        self.set_onmessage(listener);
371        // Note: we cannot use the event_handler macro, due to the need to start the port.
372        self.global()
373            .start_message_port(self.message_port_id(), can_gc);
374    }
375
376    // <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessageerror>
377    event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror);
378
379    // <https://html.spec.whatwg.org/multipage/#handler-messageport-onclose>
380    event_handler!(close, GetOnclose, SetOnclose);
381}