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