script/dom/
websocket.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::borrow::ToOwned;
6use std::cell::Cell;
7use std::ptr::{self, NonNull};
8
9use base::generic_channel::{LazyCallback, lazy_callback};
10use constellation_traits::BlobImpl;
11use dom_struct::dom_struct;
12use ipc_channel::router::ROUTER;
13use js::jsapi::JSObject;
14use js::jsval::UndefinedValue;
15use js::realm::AutoRealm;
16use js::rust::{CustomAutoRooterGuard, HandleObject};
17use js::typedarray::{ArrayBuffer, ArrayBufferView, CreateWith};
18use net_traits::request::{
19    CacheMode, CredentialsMode, RedirectMode, Referrer, RequestBuilder, RequestMode,
20    ServiceWorkersMode,
21};
22use net_traits::{
23    CoreResourceMsg, FetchChannels, MessageData, WebSocketDomAction, WebSocketNetworkEvent,
24};
25use profile_traits::ipc as ProfiledIpc;
26use script_bindings::conversions::SafeToJSValConvertible;
27use servo_url::{ImmutableOrigin, ServoUrl};
28
29use crate::dom::bindings::cell::DomRefCell;
30use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
31use crate::dom::bindings::codegen::Bindings::WebSocketBinding::{BinaryType, WebSocketMethods};
32use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
33use crate::dom::bindings::codegen::UnionTypes::StringOrStringSequence;
34use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
35use crate::dom::bindings::inheritance::Castable;
36use crate::dom::bindings::refcounted::Trusted;
37use crate::dom::bindings::reflector::{DomGlobal, DomObject, reflect_dom_object_with_proto};
38use crate::dom::bindings::root::DomRoot;
39use crate::dom::bindings::str::{DOMString, USVString, is_token};
40use crate::dom::blob::Blob;
41use crate::dom::closeevent::CloseEvent;
42use crate::dom::csp::{GlobalCspReporting, Violation};
43use crate::dom::event::{Event, EventBubbles, EventCancelable};
44use crate::dom::eventtarget::EventTarget;
45use crate::dom::globalscope::GlobalScope;
46use crate::dom::messageevent::MessageEvent;
47use crate::dom::window::Window;
48use crate::fetch::RequestWithGlobalScope;
49use crate::script_runtime::CanGc;
50use crate::task::TaskOnce;
51use crate::task_source::SendableTaskSource;
52
53#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
54enum WebSocketRequestState {
55    Connecting = 0,
56    Open = 1,
57    Closing = 2,
58    Closed = 3,
59}
60
61// Close codes defined in https://tools.ietf.org/html/rfc6455#section-7.4.1
62// Names are from https://github.com/mozilla/gecko-dev/blob/master/netwerk/protocol/websocket/nsIWebSocketChannel.idl
63#[expect(dead_code)]
64mod close_code {
65    pub(crate) const NORMAL: u16 = 1000;
66    pub(crate) const GOING_AWAY: u16 = 1001;
67    pub(crate) const PROTOCOL_ERROR: u16 = 1002;
68    pub(crate) const UNSUPPORTED_DATATYPE: u16 = 1003;
69    pub(crate) const NO_STATUS: u16 = 1005;
70    pub(crate) const ABNORMAL: u16 = 1006;
71    pub(crate) const INVALID_PAYLOAD: u16 = 1007;
72    pub(crate) const POLICY_VIOLATION: u16 = 1008;
73    pub(crate) const TOO_LARGE: u16 = 1009;
74    pub(crate) const EXTENSION_MISSING: u16 = 1010;
75    pub(crate) const INTERNAL_ERROR: u16 = 1011;
76    pub(crate) const TLS_FAILED: u16 = 1015;
77}
78
79fn close_the_websocket_connection(
80    address: Trusted<WebSocket>,
81    task_source: &SendableTaskSource,
82    code: Option<u16>,
83    reason: String,
84) {
85    task_source.queue(CloseTask {
86        address,
87        failed: false,
88        code,
89        reason: Some(reason),
90    });
91}
92
93fn fail_the_websocket_connection(address: Trusted<WebSocket>, task_source: &SendableTaskSource) {
94    task_source.queue(CloseTask {
95        address,
96        failed: true,
97        code: Some(close_code::ABNORMAL),
98        reason: None,
99    });
100}
101
102#[dom_struct]
103pub(crate) struct WebSocket {
104    eventtarget: EventTarget,
105    #[no_trace]
106    url: ServoUrl,
107    ready_state: Cell<WebSocketRequestState>,
108    buffered_amount: Cell<u64>,
109    clearing_buffer: Cell<bool>, // Flag to tell if there is a running thread to clear buffered_amount
110    #[no_trace]
111    callback: LazyCallback<WebSocketDomAction>,
112    binary_type: Cell<BinaryType>,
113    protocol: DomRefCell<String>, // Subprotocol selected by server
114}
115
116impl WebSocket {
117    fn new_inherited(url: ServoUrl, callback: LazyCallback<WebSocketDomAction>) -> WebSocket {
118        WebSocket {
119            eventtarget: EventTarget::new_inherited(),
120            url,
121            ready_state: Cell::new(WebSocketRequestState::Connecting),
122            buffered_amount: Cell::new(0),
123            clearing_buffer: Cell::new(false),
124            callback,
125            binary_type: Cell::new(BinaryType::Blob),
126            protocol: DomRefCell::new("".to_owned()),
127        }
128    }
129
130    fn new(
131        global: &GlobalScope,
132        proto: Option<HandleObject>,
133        url: ServoUrl,
134        sender: LazyCallback<WebSocketDomAction>,
135        can_gc: CanGc,
136    ) -> DomRoot<WebSocket> {
137        let websocket = reflect_dom_object_with_proto(
138            Box::new(WebSocket::new_inherited(url, sender)),
139            global,
140            proto,
141            can_gc,
142        );
143        if let Some(window) = global.downcast::<Window>() {
144            window.Document().track_websocket(&websocket);
145        }
146        websocket
147    }
148
149    /// <https://html.spec.whatwg.org/multipage/#dom-websocket-send>
150    fn send_impl(&self, data_byte_len: u64) -> Fallible<bool> {
151        let return_after_buffer = match self.ready_state.get() {
152            WebSocketRequestState::Connecting => {
153                return Err(Error::InvalidState(None));
154            },
155            WebSocketRequestState::Open => false,
156            WebSocketRequestState::Closing | WebSocketRequestState::Closed => true,
157        };
158
159        let address = Trusted::new(self);
160
161        match data_byte_len.checked_add(self.buffered_amount.get()) {
162            None => panic!(),
163            Some(new_amount) => self.buffered_amount.set(new_amount),
164        };
165
166        if return_after_buffer {
167            return Ok(false);
168        }
169
170        if !self.clearing_buffer.get() && self.ready_state.get() == WebSocketRequestState::Open {
171            self.clearing_buffer.set(true);
172
173            // TODO(mrobinson): Should this task be cancellable?
174            self.global()
175                .task_manager()
176                .websocket_task_source()
177                .queue_unconditionally(BufferedAmountTask { address });
178        }
179
180        Ok(true)
181    }
182
183    pub(crate) fn origin(&self) -> ImmutableOrigin {
184        self.url.origin()
185    }
186
187    /// <https://websockets.spec.whatwg.org/#make-disappear>
188    /// Returns true if any action was taken.
189    pub(crate) fn make_disappear(&self) -> bool {
190        let result = self.ready_state.get() != WebSocketRequestState::Closed;
191        let _ = self.Close(Some(1001), None);
192        result
193    }
194}
195
196impl WebSocketMethods<crate::DomTypeHolder> for WebSocket {
197    /// <https://html.spec.whatwg.org/multipage/#dom-websocket>
198    fn Constructor(
199        global: &GlobalScope,
200        proto: Option<HandleObject>,
201        can_gc: CanGc,
202        url: DOMString,
203        protocols: Option<StringOrStringSequence>,
204    ) -> Fallible<DomRoot<WebSocket>> {
205        // Step 1. Let baseURL be this's relevant settings object's API base URL.
206        let base_url = global.api_base_url();
207        // Step 2. Let urlRecord be the result of applying the URL parser to url with baseURL.
208        // Step 3. If urlRecord is failure, then throw a "SyntaxError" DOMException.
209        let mut url_record =
210            ServoUrl::parse_with_base(Some(&base_url), &url.str()).or(Err(Error::Syntax(None)))?;
211
212        // Step 4. If urlRecord’s scheme is "http", then set urlRecord’s scheme to "ws".
213        // Step 5. Otherwise, if urlRecord’s scheme is "https", set urlRecord’s scheme to "wss".
214        // Step 6. If urlRecord’s scheme is not "ws" or "wss", then throw a "SyntaxError" DOMException.
215        match url_record.scheme() {
216            "http" => {
217                url_record
218                    .as_mut_url()
219                    .set_scheme("ws")
220                    .expect("Can't set scheme from http to ws");
221            },
222            "https" => {
223                url_record
224                    .as_mut_url()
225                    .set_scheme("wss")
226                    .expect("Can't set scheme from https to wss");
227            },
228            "ws" | "wss" => {},
229            _ => return Err(Error::Syntax(None)),
230        }
231
232        // Step 7. If urlRecord’s fragment is non-null, then throw a "SyntaxError" DOMException.
233        if url_record.fragment().is_some() {
234            return Err(Error::Syntax(None));
235        }
236
237        // Step 8. If protocols is a string, set protocols to a sequence consisting of just that string.
238        let protocols = protocols.map_or(vec![], |p| match p {
239            StringOrStringSequence::String(string) => vec![string.into()],
240            StringOrStringSequence::StringSequence(seq) => {
241                seq.into_iter().map(String::from).collect()
242            },
243        });
244
245        // Step 9. If any of the values in protocols occur more than once or otherwise fail to match the requirements
246        // for elements that comprise the value of `Sec-WebSocket-Protocol` fields as defined by The WebSocket protocol,
247        // then throw a "SyntaxError" DOMException.
248        for (i, protocol) in protocols.iter().enumerate() {
249            // https://tools.ietf.org/html/rfc6455#section-4.1
250            // Handshake requirements, step 10
251
252            if protocols[i + 1..]
253                .iter()
254                .any(|p| p.eq_ignore_ascii_case(protocol))
255            {
256                return Err(Error::Syntax(None));
257            }
258
259            // https://tools.ietf.org/html/rfc6455#section-4.1
260            if !is_token(protocol.as_bytes()) {
261                return Err(Error::Syntax(None));
262            }
263        }
264
265        // Create the interface for communication with the resource thread
266        let (dom_action_sender, resource_action_receiver) = lazy_callback();
267        let (resource_event_sender, dom_event_receiver) =
268            ProfiledIpc::channel(global.time_profiler_chan().clone()).unwrap();
269
270        // Step 12. Establish a WebSocket connection given urlRecord, protocols, and client.
271        let ws = WebSocket::new(global, proto, url_record.clone(), dom_action_sender, can_gc);
272        let address = Trusted::new(&*ws);
273
274        // https://websockets.spec.whatwg.org/#concept-websocket-establish
275        //
276        // Let request be a new request, whose URL is requestURL, client is client, service-workers
277        // mode is "none", referrer is "no-referrer", mode is "websocket", credentials mode is
278        // "include", cache mode is "no-store" , and redirect mode is "error"
279        let request = RequestBuilder::new(
280            global.webview_id(),
281            url_record.clone(),
282            Referrer::NoReferrer,
283        )
284        .with_global_scope(global)
285        .mode(RequestMode::WebSocket {
286            protocols,
287            original_url: url_record,
288        })
289        .service_workers_mode(ServiceWorkersMode::None)
290        .credentials_mode(CredentialsMode::Include)
291        .cache_mode(CacheMode::NoCache)
292        .redirect_mode(RedirectMode::Error);
293
294        let channels = FetchChannels::WebSocket {
295            event_sender: resource_event_sender,
296            action_receiver: resource_action_receiver,
297        };
298        let _ = global
299            .core_resource_thread()
300            .send(CoreResourceMsg::Fetch(request, channels));
301
302        let task_source = global.task_manager().websocket_task_source().to_sendable();
303        ROUTER.add_typed_route(
304            dom_event_receiver.to_ipc_receiver(),
305            Box::new(move |message| match message.unwrap() {
306                WebSocketNetworkEvent::ReportCSPViolations(violations) => {
307                    let task = ReportCSPViolationTask {
308                        websocket: address.clone(),
309                        violations,
310                    };
311                    task_source.queue(task);
312                },
313                WebSocketNetworkEvent::ConnectionEstablished { protocol_in_use } => {
314                    let open_thread = ConnectionEstablishedTask {
315                        address: address.clone(),
316                        protocol_in_use,
317                    };
318                    task_source.queue(open_thread);
319                },
320                WebSocketNetworkEvent::MessageReceived(message) => {
321                    let message_thread = MessageReceivedTask {
322                        address: address.clone(),
323                        message,
324                    };
325                    task_source.queue(message_thread);
326                },
327                WebSocketNetworkEvent::Fail => {
328                    fail_the_websocket_connection(address.clone(), &task_source);
329                },
330                WebSocketNetworkEvent::Close(code, reason) => {
331                    close_the_websocket_connection(address.clone(), &task_source, code, reason);
332                },
333            }),
334        );
335
336        Ok(ws)
337    }
338
339    // https://html.spec.whatwg.org/multipage/#handler-websocket-onopen
340    event_handler!(open, GetOnopen, SetOnopen);
341
342    // https://html.spec.whatwg.org/multipage/#handler-websocket-onclose
343    event_handler!(close, GetOnclose, SetOnclose);
344
345    // https://html.spec.whatwg.org/multipage/#handler-websocket-onerror
346    event_handler!(error, GetOnerror, SetOnerror);
347
348    // https://html.spec.whatwg.org/multipage/#handler-websocket-onmessage
349    event_handler!(message, GetOnmessage, SetOnmessage);
350
351    /// <https://html.spec.whatwg.org/multipage/#dom-websocket-url>
352    fn Url(&self) -> DOMString {
353        DOMString::from(self.url.as_str())
354    }
355
356    /// <https://html.spec.whatwg.org/multipage/#dom-websocket-readystate>
357    fn ReadyState(&self) -> u16 {
358        self.ready_state.get() as u16
359    }
360
361    /// <https://html.spec.whatwg.org/multipage/#dom-websocket-bufferedamount>
362    fn BufferedAmount(&self) -> u64 {
363        self.buffered_amount.get()
364    }
365
366    /// <https://html.spec.whatwg.org/multipage/#dom-websocket-binarytype>
367    fn BinaryType(&self) -> BinaryType {
368        self.binary_type.get()
369    }
370
371    /// <https://html.spec.whatwg.org/multipage/#dom-websocket-binarytype>
372    fn SetBinaryType(&self, btype: BinaryType) {
373        self.binary_type.set(btype)
374    }
375
376    /// <https://html.spec.whatwg.org/multipage/#dom-websocket-protocol>
377    fn Protocol(&self) -> DOMString {
378        DOMString::from(self.protocol.borrow().clone())
379    }
380
381    /// <https://html.spec.whatwg.org/multipage/#dom-websocket-send>
382    fn Send(&self, data: USVString) -> ErrorResult {
383        let data_byte_len = data.0.len() as u64;
384        let send_data = self.send_impl(data_byte_len)?;
385
386        if send_data {
387            let _ = self
388                .callback
389                .send(WebSocketDomAction::SendMessage(MessageData::Text(data.0)));
390        }
391
392        Ok(())
393    }
394
395    /// <https://html.spec.whatwg.org/multipage/#dom-websocket-send>
396    fn Send_(&self, blob: &Blob) -> ErrorResult {
397        /* As per https://html.spec.whatwg.org/multipage/#websocket
398           the buffered amount needs to be clamped to u32, even though Blob.Size() is u64
399           If the buffer limit is reached in the first place, there are likely other major problems
400        */
401        let data_byte_len = blob.Size();
402        let send_data = self.send_impl(data_byte_len)?;
403
404        if send_data {
405            let bytes = blob.get_bytes().unwrap_or_default();
406            let _ = self
407                .callback
408                .send(WebSocketDomAction::SendMessage(MessageData::Binary(bytes)));
409        }
410
411        Ok(())
412    }
413
414    /// <https://html.spec.whatwg.org/multipage/#dom-websocket-send>
415    fn Send__(&self, array: CustomAutoRooterGuard<ArrayBuffer>) -> ErrorResult {
416        let bytes = array.to_vec();
417        let data_byte_len = bytes.len();
418        let send_data = self.send_impl(data_byte_len as u64)?;
419
420        if send_data {
421            let _ = self
422                .callback
423                .send(WebSocketDomAction::SendMessage(MessageData::Binary(bytes)));
424        }
425        Ok(())
426    }
427
428    /// <https://html.spec.whatwg.org/multipage/#dom-websocket-send>
429    fn Send___(&self, array: CustomAutoRooterGuard<ArrayBufferView>) -> ErrorResult {
430        let bytes = array.to_vec();
431        let data_byte_len = bytes.len();
432        let send_data = self.send_impl(data_byte_len as u64)?;
433
434        if send_data {
435            let _ = self
436                .callback
437                .send(WebSocketDomAction::SendMessage(MessageData::Binary(bytes)));
438        }
439        Ok(())
440    }
441
442    /// <https://html.spec.whatwg.org/multipage/#dom-websocket-close>
443    fn Close(&self, code: Option<u16>, reason: Option<USVString>) -> ErrorResult {
444        if let Some(code) = code {
445            // Fail if the supplied code isn't normal and isn't reserved for libraries, frameworks, and applications
446            if code != close_code::NORMAL && !(3000..=4999).contains(&code) {
447                return Err(Error::InvalidAccess(None));
448            }
449        }
450        if let Some(ref reason) = reason {
451            if reason.0.len() > 123 {
452                // reason cannot be larger than 123 bytes
453                return Err(Error::Syntax(Some("Reason too long".to_string())));
454            }
455        }
456
457        match self.ready_state.get() {
458            WebSocketRequestState::Closing | WebSocketRequestState::Closed => {}, // Do nothing
459            WebSocketRequestState::Connecting => {
460                // Connection is not yet established
461                /*By setting the state to closing, the open function
462                will abort connecting the websocket*/
463                self.ready_state.set(WebSocketRequestState::Closing);
464
465                fail_the_websocket_connection(
466                    Trusted::new(self),
467                    &self
468                        .global()
469                        .task_manager()
470                        .websocket_task_source()
471                        .to_sendable(),
472                );
473            },
474            WebSocketRequestState::Open => {
475                self.ready_state.set(WebSocketRequestState::Closing);
476
477                // Kick off _Start the WebSocket Closing Handshake_
478                // https://tools.ietf.org/html/rfc6455#section-7.1.2
479                let reason = reason.map(|reason| reason.0);
480                let _ = self.callback.send(WebSocketDomAction::Close(code, reason));
481            },
482        }
483        Ok(()) // Return Ok
484    }
485}
486
487struct ReportCSPViolationTask {
488    websocket: Trusted<WebSocket>,
489    violations: Vec<Violation>,
490}
491
492impl TaskOnce for ReportCSPViolationTask {
493    fn run_once(self, _cx: &mut js::context::JSContext) {
494        let global = self.websocket.root().global();
495        global.report_csp_violations(self.violations, None, None);
496    }
497}
498
499/// Task queued when *the WebSocket connection is established*.
500/// <https://html.spec.whatwg.org/multipage/#feedback-from-the-protocol:concept-websocket-established>
501struct ConnectionEstablishedTask {
502    address: Trusted<WebSocket>,
503    protocol_in_use: Option<String>,
504}
505
506impl TaskOnce for ConnectionEstablishedTask {
507    /// <https://html.spec.whatwg.org/multipage/#feedback-from-the-protocol:concept-websocket-established>
508    fn run_once(self, cx: &mut js::context::JSContext) {
509        let ws = self.address.root();
510
511        // Step 1.
512        ws.ready_state.set(WebSocketRequestState::Open);
513
514        // Step 2: Extensions.
515        // TODO: Set extensions to extensions in use.
516
517        // Step 3.
518        if let Some(protocol_name) = self.protocol_in_use {
519            *ws.protocol.borrow_mut() = protocol_name;
520        };
521
522        // Step 4.
523        ws.upcast().fire_event(atom!("open"), CanGc::from_cx(cx));
524    }
525}
526
527struct BufferedAmountTask {
528    address: Trusted<WebSocket>,
529}
530
531impl TaskOnce for BufferedAmountTask {
532    // See https://html.spec.whatwg.org/multipage/#dom-websocket-bufferedamount
533    //
534    // To be compliant with standards, we need to reset bufferedAmount only when the event loop
535    // reaches step 1.  In our implementation, the bytes will already have been sent on a background
536    // thread.
537    fn run_once(self, _cx: &mut js::context::JSContext) {
538        let ws = self.address.root();
539
540        ws.buffered_amount.set(0);
541        ws.clearing_buffer.set(false);
542    }
543}
544
545struct CloseTask {
546    address: Trusted<WebSocket>,
547    failed: bool,
548    code: Option<u16>,
549    reason: Option<String>,
550}
551
552impl TaskOnce for CloseTask {
553    fn run_once(self, cx: &mut js::context::JSContext) {
554        let ws = self.address.root();
555
556        if ws.ready_state.get() == WebSocketRequestState::Closed {
557            // Do nothing if already closed.
558            return;
559        }
560
561        // Perform _the WebSocket connection is closed_ steps.
562        // https://html.spec.whatwg.org/multipage/#closeWebSocket
563
564        // Step 1.
565        ws.ready_state.set(WebSocketRequestState::Closed);
566
567        // Step 2.
568        if self.failed {
569            ws.upcast().fire_event(atom!("error"), CanGc::from_cx(cx));
570        }
571
572        // Step 3.
573        let clean_close = !self.failed;
574        let code = self.code.unwrap_or(close_code::NO_STATUS);
575        let reason = DOMString::from(self.reason.unwrap_or("".to_owned()));
576        let close_event = CloseEvent::new(
577            &ws.global(),
578            atom!("close"),
579            EventBubbles::DoesNotBubble,
580            EventCancelable::NotCancelable,
581            clean_close,
582            code,
583            reason,
584            CanGc::from_cx(cx),
585        );
586        close_event
587            .upcast::<Event>()
588            .fire(ws.upcast(), CanGc::from_cx(cx));
589    }
590}
591
592struct MessageReceivedTask {
593    address: Trusted<WebSocket>,
594    message: MessageData,
595}
596
597impl TaskOnce for MessageReceivedTask {
598    #[expect(unsafe_code)]
599    fn run_once(self, cx: &mut js::context::JSContext) {
600        let ws = self.address.root();
601        debug!(
602            "MessageReceivedTask::handler({:p}): readyState={:?}",
603            &*ws,
604            ws.ready_state.get()
605        );
606
607        // Step 1.
608        if ws.ready_state.get() != WebSocketRequestState::Open {
609            return;
610        }
611
612        // Step 2-5.
613        let global = ws.global();
614        let mut realm = AutoRealm::new(
615            cx,
616            NonNull::new(ws.reflector().get_jsobject().get()).unwrap(),
617        );
618        let cx = &mut *realm;
619        rooted!(&in(cx) let mut message = UndefinedValue());
620        match self.message {
621            MessageData::Text(text) => {
622                text.safe_to_jsval(cx.into(), message.handle_mut(), CanGc::from_cx(cx))
623            },
624            MessageData::Binary(data) => match ws.binary_type.get() {
625                BinaryType::Blob => {
626                    let blob = Blob::new(
627                        &global,
628                        BlobImpl::new_from_bytes(data, "".to_owned()),
629                        CanGc::from_cx(cx),
630                    );
631                    blob.safe_to_jsval(cx.into(), message.handle_mut(), CanGc::from_cx(cx));
632                },
633                BinaryType::Arraybuffer => {
634                    rooted!(&in(cx) let mut array_buffer = ptr::null_mut::<JSObject>());
635                    // GlobalScope::get_cx() returns a valid `JSContext` pointer, so this is safe.
636                    unsafe {
637                        assert!(
638                            ArrayBuffer::create(
639                                cx.raw_cx(),
640                                CreateWith::Slice(&data),
641                                array_buffer.handle_mut()
642                            )
643                            .is_ok()
644                        )
645                    };
646
647                    (*array_buffer).safe_to_jsval(
648                        cx.into(),
649                        message.handle_mut(),
650                        CanGc::from_cx(cx),
651                    );
652                },
653            },
654        }
655        MessageEvent::dispatch_jsval(
656            ws.upcast(),
657            &global,
658            message.handle(),
659            Some(&ws.origin().ascii_serialization()),
660            None,
661            vec![],
662            CanGc::from_cx(cx),
663        );
664    }
665}