diff options
Diffstat (limited to 'components/script/dom/messageport.rs')
-rw-r--r-- | components/script/dom/messageport.rs | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/components/script/dom/messageport.rs b/components/script/dom/messageport.rs new file mode 100644 index 00000000000..fee7c75a9eb --- /dev/null +++ b/components/script/dom/messageport.rs @@ -0,0 +1,342 @@ +/* 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 crate::dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; +use crate::dom::bindings::codegen::Bindings::MessagePortBinding::{ + MessagePortMethods, PostMessageOptions, +}; +use crate::dom::bindings::conversions::root_from_object; +use crate::dom::bindings::error::{Error, ErrorResult}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::structuredclone::{self, StructuredDataHolder}; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::bindings::transferable::Transferable; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::script_runtime::JSContext as SafeJSContext; +use dom_struct::dom_struct; +use js::jsapi::Heap; +use js::jsapi::{JSObject, MutableHandleObject}; +use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue}; +use msg::constellation_msg::{MessagePortId, MessagePortIndex, PipelineNamespaceId}; +use script_traits::PortMessageTask; +use std::cell::{Cell, RefCell}; +use std::collections::HashMap; +use std::convert::TryInto; +use std::num::NonZeroU32; +use std::rc::Rc; + +#[dom_struct] +/// The MessagePort used in the DOM. +pub struct MessagePort { + eventtarget: EventTarget, + message_port_id: MessagePortId, + entangled_port: RefCell<Option<MessagePortId>>, + detached: Cell<bool>, +} + +impl MessagePort { + fn new_inherited(message_port_id: MessagePortId) -> MessagePort { + MessagePort { + eventtarget: EventTarget::new_inherited(), + entangled_port: RefCell::new(None), + detached: Cell::new(false), + message_port_id, + } + } + + /// <https://html.spec.whatwg.org/multipage/#create-a-new-messageport-object> + pub fn new(owner: &GlobalScope) -> DomRoot<MessagePort> { + let port_id = MessagePortId::new(); + reflect_dom_object(Box::new(MessagePort::new_inherited(port_id)), owner) + } + + /// Create a new port for an incoming transfer-received one. + fn new_transferred( + owner: &GlobalScope, + transferred_port: MessagePortId, + entangled_port: Option<MessagePortId>, + ) -> DomRoot<MessagePort> { + reflect_dom_object( + Box::new(MessagePort { + message_port_id: transferred_port, + eventtarget: EventTarget::new_inherited(), + detached: Cell::new(false), + entangled_port: RefCell::new(entangled_port), + }), + owner, + ) + } + + /// <https://html.spec.whatwg.org/multipage/#entangle> + pub fn entangle(&self, other_id: MessagePortId) { + *self.entangled_port.borrow_mut() = Some(other_id); + } + + pub fn message_port_id(&self) -> &MessagePortId { + &self.message_port_id + } + + pub fn detached(&self) -> bool { + self.detached.get() + } + + /// <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessage> + fn set_onmessage(&self, listener: Option<Rc<EventHandlerNonNull>>) { + let eventtarget = self.upcast::<EventTarget>(); + eventtarget.set_event_handler_common("message", listener); + } + + /// <https://html.spec.whatwg.org/multipage/#message-port-post-message-steps> + fn post_message_impl( + &self, + cx: SafeJSContext, + message: HandleValue, + transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>, + ) -> ErrorResult { + if self.detached.get() { + return Ok(()); + } + + // Step 1 is the transfer argument. + + let target_port = self.entangled_port.borrow(); + + // Step 3 + let mut doomed = false; + + let ports = transfer + .iter() + .filter_map(|&obj| root_from_object::<MessagePort>(obj, *cx).ok()); + for port in ports { + // Step 2 + if port.message_port_id() == self.message_port_id() { + return Err(Error::DataClone); + } + + // Step 4 + if let Some(target_id) = target_port.as_ref() { + if port.message_port_id() == target_id { + doomed = true; + } + } + } + + // Step 5 + let data = structuredclone::write(cx, message, Some(transfer))?; + + if doomed { + // TODO: The spec says to optionally report such a case to a dev console. + return Ok(()); + } + + // Step 6, done in MessagePortImpl. + + let incumbent = match GlobalScope::incumbent() { + None => unreachable!("postMessage called with no incumbent global"), + Some(incumbent) => incumbent, + }; + + // Step 7 + let task = PortMessageTask { + origin: incumbent.origin().immutable().clone(), + data, + }; + + // Have the global proxy this call to the corresponding MessagePortImpl. + self.global() + .post_messageport_msg(self.message_port_id().clone(), task); + Ok(()) + } +} + +impl Transferable for MessagePort { + /// <https://html.spec.whatwg.org/multipage/#message-ports:transfer-steps> + fn transfer(&self, sc_holder: &mut StructuredDataHolder) -> Result<u64, ()> { + if self.detached.get() { + return Err(()); + } + + let port_impls = match sc_holder { + StructuredDataHolder::Write { ports, .. } => ports, + _ => panic!("Unexpected variant of StructuredDataHolder"), + }; + + self.detached.set(true); + let id = self.message_port_id(); + + // 1. Run local transfer logic, and return the object to be transferred. + let transferred_port = self.global().mark_port_as_transferred(id); + + // 2. Store the transferred object at a given key. + if let Some(ports) = port_impls.as_mut() { + ports.insert(id.clone(), transferred_port); + } else { + let mut ports = HashMap::new(); + ports.insert(id.clone(), transferred_port); + *port_impls = Some(ports); + } + + let PipelineNamespaceId(name_space) = id.clone().namespace_id; + let MessagePortIndex(index) = id.clone().index; + let index = index.get(); + + let mut big: [u8; 8] = [0; 8]; + let name_space = name_space.to_ne_bytes(); + let index = index.to_ne_bytes(); + + let (left, right) = big.split_at_mut(4); + left.copy_from_slice(&name_space); + right.copy_from_slice(&index); + + // 3. Return a u64 representation of the key where the object is stored. + Ok(u64::from_ne_bytes(big)) + } + + /// https://html.spec.whatwg.org/multipage/#message-ports:transfer-receiving-steps + fn transfer_receive( + owner: &GlobalScope, + sc_holder: &mut StructuredDataHolder, + extra_data: u64, + return_object: MutableHandleObject, + ) -> Result<(), ()> { + let (message_ports, port_impls) = match sc_holder { + StructuredDataHolder::Read { + message_ports, + port_impls, + .. + } => (message_ports, port_impls), + _ => panic!("Unexpected variant of StructuredDataHolder"), + }; + + // 1. Re-build the key for the storage location + // of the transferred object. + let big: [u8; 8] = extra_data.to_ne_bytes(); + let (name_space, index) = big.split_at(4); + + let namespace_id = PipelineNamespaceId(u32::from_ne_bytes( + name_space + .try_into() + .expect("name_space to be a slice of four."), + )); + let index = MessagePortIndex( + NonZeroU32::new(u32::from_ne_bytes( + index.try_into().expect("index to be a slice of four."), + )) + .expect("Index to be non-zero"), + ); + + let id = MessagePortId { + namespace_id, + index, + }; + + // 2. Get the transferred object from its storage, using the key. + // Assign the transfer-received port-impl, and total number of transferred ports. + let (ports_len, port_impl) = if let Some(ports) = port_impls.as_mut() { + let ports_len = ports.len(); + let port_impl = ports.remove(&id).expect("Transferred port to be stored"); + if ports.is_empty() { + *port_impls = None; + } + (ports_len, port_impl) + } else { + panic!("A messageport was transfer-received, yet the SC holder does not have any port impls"); + }; + + let transferred_port = + MessagePort::new_transferred(&*owner, id.clone(), port_impl.entangled_port_id()); + owner.track_message_port(&transferred_port, Some(port_impl)); + + return_object.set(transferred_port.reflector().rootable().get()); + + // Store the DOM port where it will be passed along to script in the message-event. + if let Some(ports) = message_ports.as_mut() { + ports.push(transferred_port); + } else { + let mut ports = Vec::with_capacity(ports_len); + ports.push(transferred_port); + *message_ports = Some(ports); + } + + Ok(()) + } +} + +impl MessagePortMethods for MessagePort { + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-postmessage> + fn PostMessage( + &self, + cx: SafeJSContext, + message: HandleValue, + transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>, + ) -> ErrorResult { + if self.detached.get() { + return Ok(()); + } + self.post_message_impl(cx, message, transfer) + } + + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-postmessage> + fn PostMessage_( + &self, + cx: SafeJSContext, + message: HandleValue, + options: RootedTraceableBox<PostMessageOptions>, + ) -> ErrorResult { + if self.detached.get() { + return Ok(()); + } + let mut rooted = CustomAutoRooter::new( + options + .transfer + .iter() + .map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get()) + .collect(), + ); + let guard = CustomAutoRooterGuard::new(*cx, &mut rooted); + self.post_message_impl(cx, message, guard) + } + + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-start> + fn Start(&self) { + if self.detached.get() { + return; + } + self.global().start_message_port(self.message_port_id()); + } + + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-close> + fn Close(&self) { + if self.detached.get() { + return; + } + self.detached.set(true); + self.global().close_message_port(self.message_port_id()); + } + + /// <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessage> + fn GetOnmessage(&self) -> Option<Rc<EventHandlerNonNull>> { + if self.detached.get() { + return None; + } + let eventtarget = self.upcast::<EventTarget>(); + eventtarget.get_event_handler_common("message") + } + + /// <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessage> + fn SetOnmessage(&self, listener: Option<Rc<EventHandlerNonNull>>) { + if self.detached.get() { + return; + } + self.set_onmessage(listener); + // Note: we cannot use the event_handler macro, due to the need to start the port. + self.global().start_message_port(self.message_port_id()); + } + + // <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessageerror> + event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror); +} |