diff options
Diffstat (limited to 'components/script/dom')
-rw-r--r-- | components/script/dom/closeevent.rs | 89 | ||||
-rw-r--r-- | components/script/dom/event.rs | 3 | ||||
-rw-r--r-- | components/script/dom/mod.rs | 1 | ||||
-rw-r--r-- | components/script/dom/webidls/CloseEvent.webidl | 17 | ||||
-rw-r--r-- | components/script/dom/webidls/WebSocket.webidl | 24 | ||||
-rw-r--r-- | components/script/dom/websocket.rs | 281 |
6 files changed, 394 insertions, 21 deletions
diff --git a/components/script/dom/closeevent.rs b/components/script/dom/closeevent.rs new file mode 100644 index 00000000000..9acc40c6823 --- /dev/null +++ b/components/script/dom/closeevent.rs @@ -0,0 +1,89 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use dom::bindings::cell::DOMRefCell; +use dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use dom::bindings::codegen::Bindings::CloseEventBinding; +use dom::bindings::codegen::Bindings::CloseEventBinding::CloseEventMethods; +use dom::bindings::codegen::InheritTypes::EventCast; +use dom::bindings::error::Fallible; +use dom::bindings::global::GlobalRef; +use dom::bindings::js::{JSRef,Temporary, Rootable}; +use dom::bindings::utils::reflect_dom_object; +use dom::event::{Event, EventTypeId, EventBubbles, EventCancelable}; +use script_task::ScriptChan; +use std::borrow::ToOwned; +use std::cell::Cell; +use util::str::DOMString; + + +#[dom_struct] +pub struct CloseEvent{ + event: Event, + wasClean: Cell<bool>, + code: Cell<u16>, + reason: DOMRefCell<DOMString> +} + +impl CloseEvent{ + pub fn new_inherited(type_id: EventTypeId) -> CloseEvent{ + CloseEvent{ + event: Event::new_inherited(type_id), + wasClean: Cell::new(true), + code: Cell::new(0), + reason: DOMRefCell::new("".to_owned()) + } + } + + pub fn new(global: GlobalRef, + type_: DOMString, + bubbles: EventBubbles, + cancelable: EventCancelable, + wasClean: bool, + code: u16, + reason: DOMString) -> Temporary<CloseEvent> { + let ev = reflect_dom_object(box CloseEvent::new_inherited(EventTypeId::CloseEvent), + global, + CloseEventBinding::Wrap); + let ev = ev.root(); + let event: JSRef<Event> = EventCast::from_ref(ev.r()); + event.InitEvent(type_, + bubbles == EventBubbles::Bubbles, + cancelable == EventCancelable::Cancelable); + let ev = ev.r(); + ev.wasClean.set(wasClean); + ev.code.set(code); + *ev.reason.borrow_mut() = reason; + Temporary::from_rooted(ev) + } + + pub fn Constructor(global: GlobalRef, + type_: DOMString, + init: &CloseEventBinding::CloseEventInit) -> Fallible<Temporary<CloseEvent>> { + let clean_status = init.wasClean.unwrap_or(true); + let cd = init.code.unwrap_or(0); + let rsn = match init.reason.as_ref() { + Some(reason) => reason.clone(), + None => "".to_owned(), + }; + let bubbles = if init.parent.bubbles { EventBubbles::Bubbles } else { EventBubbles::DoesNotBubble }; + let cancelable = if init.parent.cancelable { EventCancelable::Cancelable } else { EventCancelable::NotCancelable }; + Ok(CloseEvent::new(global, type_, bubbles, cancelable, clean_status, cd, rsn)) + } +} + +impl<'a> CloseEventMethods for JSRef<'a, CloseEvent>{ + fn WasClean(self) -> bool { + self.wasClean.get() + } + + fn Code(self) -> u16 { + self.code.get() + } + + fn Reason(self) -> DOMString { + let reason = self.reason.borrow(); + reason.clone() + } +} diff --git a/components/script/dom/event.rs b/components/script/dom/event.rs index 4f0086310aa..012742a0d7b 100644 --- a/components/script/dom/event.rs +++ b/components/script/dom/event.rs @@ -39,7 +39,8 @@ pub enum EventTypeId { ProgressEvent, StorageEvent, UIEvent, - ErrorEvent + ErrorEvent, + CloseEvent } #[derive(PartialEq)] diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 8c78cdb1420..e693b1f70de 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -206,6 +206,7 @@ pub mod cssstyledeclaration; pub mod domrect; pub mod domrectlist; pub mod domstringmap; +pub mod closeevent; pub mod comment; pub mod console; mod create; diff --git a/components/script/dom/webidls/CloseEvent.webidl b/components/script/dom/webidls/CloseEvent.webidl new file mode 100644 index 00000000000..edc2a43de19 --- /dev/null +++ b/components/script/dom/webidls/CloseEvent.webidl @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//https://html.spec.whatwg.org/multipage/#the-closeevent-interfaces +[Constructor(DOMString type, optional CloseEventInit eventInitDict)/*, Exposed=(Window,Worker)*/] +interface CloseEvent : Event { + readonly attribute boolean wasClean; + readonly attribute unsigned short code; + readonly attribute DOMString reason; +}; + +dictionary CloseEventInit : EventInit { + boolean wasClean; + unsigned short code; + DOMString reason; +}; diff --git a/components/script/dom/webidls/WebSocket.webidl b/components/script/dom/webidls/WebSocket.webidl index 3f54ec79019..6067ca30c4f 100644 --- a/components/script/dom/webidls/WebSocket.webidl +++ b/components/script/dom/webidls/WebSocket.webidl @@ -2,26 +2,34 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +enum BinaryType { "blob", "arraybuffer" }; + [Constructor(DOMString url)] interface WebSocket : EventTarget { -readonly attribute DOMString url; - //attribute DOMString port; - //attribute DOMString host; + readonly attribute DOMString url; //ready state const unsigned short CONNECTING = 0; const unsigned short OPEN = 1; const unsigned short CLOSING = 2; const unsigned short CLOSED = 3; - //readonly attribute unsigned short readyState; + readonly attribute unsigned short readyState; //readonly attribute unsigned long bufferedAmount; + //networking - //attribute EventHandler onopen; - //attribute EventHandler onerror; - //attribute EventHandler onclose; + attribute EventHandler onopen; + attribute EventHandler onerror; + attribute EventHandler onclose; //readonly attribute DOMString extensions; //readonly attribute DOMString protocol; - //void send(USVString data); + //[Throws] void close([Clamp] optional unsigned short code, optional DOMString reason); //Clamp doesn't work + [Throws] void close(optional unsigned short code, optional DOMString reason); //No clamp version - works + + //messaging + //attribute EventHandler onmessage; + //attribute BinaryType binaryType; + [Throws] void send(optional DOMString data); //void send(Blob data); //void send(ArrayBuffer data); //void send(ArrayBufferView data); + }; diff --git a/components/script/dom/websocket.rs b/components/script/dom/websocket.rs index 1af70fa5f0b..0b7673d828b 100644 --- a/components/script/dom/websocket.rs +++ b/components/script/dom/websocket.rs @@ -2,34 +2,137 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +use dom::bindings::cell::DOMRefCell; use dom::bindings::codegen::Bindings::WebSocketBinding; use dom::bindings::codegen::Bindings::WebSocketBinding::WebSocketMethods; -use dom::bindings::error::Fallible; -use dom::bindings::global::GlobalRef; -use dom::bindings::js::{Temporary, JSRef}; +use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; +use dom::bindings::codegen::InheritTypes::EventTargetCast; +use dom::bindings::codegen::InheritTypes::EventCast; +use dom::bindings::error::{Error, Fallible}; +use dom::bindings::error::Error::InvalidAccess; +use dom::bindings::error::Error::Syntax; +use dom::bindings::global::{GlobalField, GlobalRef}; +use dom::bindings::js::{Temporary, JSRef, Rootable}; +use dom::bindings::refcounted::Trusted; +use dom::bindings::trace::JSTraceable; use dom::bindings::utils::reflect_dom_object; -use dom::eventtarget::{EventTarget, EventTargetTypeId}; +use dom::closeevent::CloseEvent; +use dom::event::{Event, EventBubbles, EventCancelable, EventHelpers}; +use dom::eventtarget::{EventTarget, EventTargetHelpers, EventTargetTypeId}; +use script_task::Runnable; +use script_task::ScriptMsg; +use std::cell::{Cell, RefCell}; +use std::borrow::ToOwned; use util::str::DOMString; -// https://html.spec.whatwg.org/#the-websocket-interface +use websocket::Message; +use websocket::ws::sender::Sender as Sender_Object; +use websocket::client::sender::Sender; +use websocket::client::receiver::Receiver; +use websocket::stream::WebSocketStream; +use websocket::client::request::Url; +use websocket::Client; + + +#[derive(PartialEq, Copy, Clone)] +#[jstraceable] +enum WebSocketRequestState { + Connecting = 0, + Open = 1, + Closing = 2, + Closed = 3, +} + +no_jsmanaged_fields!(Sender<WebSocketStream>); +no_jsmanaged_fields!(Receiver<WebSocketStream>); + #[dom_struct] pub struct WebSocket { eventtarget: EventTarget, - url: DOMString + url: DOMString, + global: GlobalField, + ready_state: Cell<WebSocketRequestState>, + sender: RefCell<Option<Sender<WebSocketStream>>>, + receiver: RefCell<Option<Receiver<WebSocketStream>>>, + failed: Cell<bool>, //Flag to tell if websocket was closed due to failure + full: Cell<bool>, //Flag to tell if websocket queue is full + clean_close: Cell<bool>, //Flag to tell if the websocket closed cleanly (not due to full or fail) + code: Cell<u16>, //Closing code + reason: DOMRefCell<DOMString>, //Closing reason + data: DOMRefCell<DOMString>, //Data from send - TODO: Remove after buffer is added. + sendCloseFrame: Cell<bool> } impl WebSocket { - pub fn new_inherited(url: DOMString) -> WebSocket { + pub fn new_inherited(global: GlobalRef, url: DOMString) -> WebSocket { WebSocket { eventtarget: EventTarget::new_inherited(EventTargetTypeId::WebSocket), - url: url + url: url, + global: GlobalField::from_rooted(&global), + ready_state: Cell::new(WebSocketRequestState::Connecting), + failed: Cell::new(false), + sender: RefCell::new(None), + receiver: RefCell::new(None), + full: Cell::new(false), + clean_close: Cell::new(true), + code: Cell::new(0), + reason: DOMRefCell::new("".to_owned()), + data: DOMRefCell::new("".to_owned()), + sendCloseFrame: Cell::new(false) } + } pub fn new(global: GlobalRef, url: DOMString) -> Temporary<WebSocket> { - reflect_dom_object(box WebSocket::new_inherited(url), - global, - WebSocketBinding::Wrap) + /*TODO: This constructor is only a prototype, it does not accomplish the specs + defined here: + http://html.spec.whatwg.org + All 9 items must be satisfied. + TODO: This constructor should be responsible for spawning a thread for the + receive loop after ws_root.r().Open() - See comment + */ + let ws_root = reflect_dom_object(box WebSocket::new_inherited(global, url), + global, + WebSocketBinding::Wrap).root(); + let ws_root = ws_root.r(); + let parsed_url = Url::parse(&ws_root.url).unwrap(); + let request = Client::connect(parsed_url).unwrap(); + let response = request.send().unwrap(); + response.validate().unwrap(); + ws_root.ready_state.set(WebSocketRequestState::Open); + //Check to see if ready_state is Closing or Closed and failed = true - means we failed the websocket + //if so return without setting any states + let ready_state = ws_root.ready_state.get(); + let failed = ws_root.failed.get(); + if failed && (ready_state == WebSocketRequestState::Closed || ready_state == WebSocketRequestState::Closing) { + //Do nothing else. Let the close finish. + return Temporary::from_rooted(ws_root); + } + let (temp_sender, temp_receiver) = response.begin().split(); + let mut other_sender = ws_root.sender.borrow_mut(); + let mut other_receiver = ws_root.receiver.borrow_mut(); + *other_sender = Some(temp_sender); + *other_receiver = Some(temp_receiver); + + //Create everything necessary for starting the open asynchronous task, then begin the task. + let global_root = ws_root.global.root(); + let addr: Trusted<WebSocket> = Trusted::new(global_root.r().get_cx(), ws_root, global_root.r().script_chan().clone()); + let open_task = box WebSocketTaskHandler::new(addr.clone(), WebSocketTask::Open); + global_root.r().script_chan().send(ScriptMsg::RunnableMsg(open_task)).unwrap(); + //TODO: Spawn thread here for receive loop + /*TODO: Add receive loop here and make new thread run this + Receive is an infinite loop "similiar" the one shown here: + https://github.com/cyderize/rust-websocket/blob/master/examples/client.rs#L64 + TODO: The receive loop however does need to follow the spec. These are outlined here + under "WebSocket message has been received" items 1-5: + https://github.com/cyderize/rust-websocket/blob/master/examples/client.rs#L64 + TODO: The receive loop also needs to dispatch an asynchronous event as stated here: + https://github.com/cyderize/rust-websocket/blob/master/examples/client.rs#L64 + TODO: When the receive loop receives a close message from the server, + it confirms the websocket is now closed. This requires the close event + to be fired (dispatch_close fires the close event - see implementation below) + */ + Temporary::from_rooted(ws_root) } pub fn Constructor(global: GlobalRef, url: DOMString) -> Fallible<Temporary<WebSocket>> { @@ -38,8 +141,162 @@ impl WebSocket { } impl<'a> WebSocketMethods for JSRef<'a, WebSocket> { - // https://html.spec.whatwg.org/#dom-websocket-url + event_handler!(open, GetOnopen, SetOnopen); + event_handler!(close, GetOnclose, SetOnclose); + event_handler!(error, GetOnerror, SetOnerror); + fn Url(self) -> DOMString { self.url.clone() } + + fn ReadyState(self) -> u16 { + self.ready_state.get() as u16 + } + + fn Send(self, data: Option<DOMString>)-> Fallible<()>{ + /*TODO: This is not up to spec see http://html.spec.whatwg.org/multipage/comms.html search for "If argument is a string" + TODO: Need to buffer data + TODO: bufferedAmount attribute returns the size of the buffer in bytes - this is a required attribute defined in the websocket.webidle file + TODO: The send function needs to flag when full by using the following + self.full.set(true). This needs to be done when the buffer is full + */ + let mut other_sender = self.sender.borrow_mut(); + let my_sender = other_sender.as_mut().unwrap(); + if self.sendCloseFrame.get() { //TODO: Also check if the buffer is full + self.sendCloseFrame.set(false); + let _ = my_sender.send_message(Message::Close(None)); + return Ok(()); + } + let _ = my_sender.send_message(Message::Text(data.unwrap())); + return Ok(()) + } + + fn Close(self, code: Option<u16>, reason: Option<DOMString>) -> Fallible<()>{ + if let Some(code) = code { + //Check code is NOT 1000 NOR in the range of 3000-4999 (inclusive) + if code != 1000 && (code < 3000 || code > 4999) { + return Err(Error::InvalidAccess); + } + } + if let Some(ref reason) = reason { + if reason.as_bytes().len() > 123 { //reason cannot be larger than 123 bytes + return Err(Error::Syntax); + } + } + + match self.ready_state.get() { + WebSocketRequestState::Closing | WebSocketRequestState::Closed => {} //Do nothing + WebSocketRequestState::Connecting => { //Connection is not yet established + /*By setting the state to closing, the open function + will abort connecting the websocket*/ + self.ready_state.set(WebSocketRequestState::Closing); + self.failed.set(true); + self.sendCloseFrame.set(true); + //Dispatch send task to send close frame + //TODO: Sending here is just empty string, though no string is really needed. Another send, empty send, could be used. + let _ = self.Send(None); + //Note: After sending the close message, the receive loop confirms a close message from the server and must fire a close event + } + WebSocketRequestState::Open => { + //Closing handshake not started - still in open + //Start the closing by setting the code and reason if they exist + if let Some(code) = code { + self.code.set(code); + } + if let Some(reason) = reason { + *self.reason.borrow_mut() = reason; + } + self.ready_state.set(WebSocketRequestState::Closing); + self.sendCloseFrame.set(true); + //Dispatch send task to send close frame + let _ = self.Send(None); + //Note: After sending the close message, the receive loop confirms a close message from the server and must fire a close event + } + } + Ok(()) //Return Ok + } } + + +pub enum WebSocketTask { + Open, + Close, +} + +pub struct WebSocketTaskHandler { + addr: Trusted<WebSocket>, + task: WebSocketTask, +} + +impl WebSocketTaskHandler { + pub fn new(addr: Trusted<WebSocket>, task: WebSocketTask) -> WebSocketTaskHandler { + WebSocketTaskHandler { + addr: addr, + task: task, + } + } + + fn dispatch_open(&self) { + /*TODO: Items 1, 3, 4, & 5 under "WebSocket connection is established" as specified here: + https://html.spec.whatwg.org/multipage/#feedback-from-the-protocol + */ + let ws = self.addr.to_temporary().root(); //Get root + let ws = ws.r(); //Get websocket reference + let global = ws.global.root(); + let event = Event::new(global.r(), + "open".to_owned(), + EventBubbles::DoesNotBubble, + EventCancelable::Cancelable).root(); + let target: JSRef<EventTarget> = EventTargetCast::from_ref(ws); + event.r().fire(target); + } + + fn dispatch_close(&self) { + let ws = self.addr.to_temporary().root(); + let ws = ws.r(); + let global = ws.global.root(); + ws.ready_state.set(WebSocketRequestState::Closed); + //If failed or full, fire error event + if ws.failed.get() || ws.full.get() { + ws.failed.set(false); + ws.full.set(false); + //A Bad close + ws.clean_close.set(false); + let event = Event::new(global.r(), + "error".to_owned(), + EventBubbles::DoesNotBubble, + EventCancelable::Cancelable).root(); + let target: JSRef<EventTarget> = EventTargetCast::from_ref(ws); + event.r().fire(target); + } + let rsn = ws.reason.borrow(); + let rsn_clone = rsn.clone(); + /*In addition, we also have to fire a close even if error event fired + https://html.spec.whatwg.org/multipage/#closeWebSocket + */ + let close_event = CloseEvent::new(global.r(), + "close".to_owned(), + EventBubbles::DoesNotBubble, + EventCancelable::Cancelable, + ws.clean_close.get(), + ws.code.get(), + rsn_clone).root(); + let target: JSRef<EventTarget> = EventTargetCast::from_ref(ws); + let event: JSRef<Event> = EventCast::from_ref(close_event.r()); + event.fire(target); + } +} + +impl Runnable for WebSocketTaskHandler { + fn handler(self: Box<WebSocketTaskHandler>) { + match self.task { + WebSocketTask::Open => { + self.dispatch_open(); + } + WebSocketTask::Close => { + self.dispatch_close(); + } + } + } +} + |