/* 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 std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::convert::TryInto; use std::num::NonZeroU32; use std::rc::Rc; use dom_struct::dom_struct; use js::jsapi::{Heap, JSObject, MutableHandleObject}; use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue}; use msg::constellation_msg::{MessagePortId, MessagePortIndex, PipelineNamespaceId}; use script_traits::PortMessageTask; 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; #[dom_struct] /// The MessagePort used in the DOM. pub struct MessagePort { eventtarget: EventTarget, #[no_trace] message_port_id: MessagePortId, #[no_trace] entangled_port: RefCell>, detached: Cell, } 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, } } /// pub fn new(owner: &GlobalScope) -> DomRoot { 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, ) -> DomRoot { 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, ) } /// 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() } /// fn set_onmessage(&self, listener: Option>) { let eventtarget = self.upcast::(); eventtarget.set_event_handler_common("message", listener); } /// fn post_message_impl( &self, cx: SafeJSContext, message: HandleValue, transfer: CustomAutoRooterGuard>, ) -> 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::(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(), task); Ok(()) } } impl Transferable for MessagePort { /// fn transfer(&self, sc_holder: &mut StructuredDataHolder) -> Result { 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, transferred_port); } else { let mut ports = HashMap::new(); ports.insert(*id, transferred_port); *port_impls = Some(ports); } let PipelineNamespaceId(name_space) = (id).namespace_id; let MessagePortIndex(index) = (id).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)) } /// 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, 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 { /// fn PostMessage( &self, cx: SafeJSContext, message: HandleValue, transfer: CustomAutoRooterGuard>, ) -> ErrorResult { if self.detached.get() { return Ok(()); } self.post_message_impl(cx, message, transfer) } /// fn PostMessage_( &self, cx: SafeJSContext, message: HandleValue, options: RootedTraceableBox, ) -> ErrorResult { if self.detached.get() { return Ok(()); } let mut rooted = CustomAutoRooter::new( options .transfer .iter() .map(|js: &RootedTraceableBox>| js.get()) .collect(), ); let guard = CustomAutoRooterGuard::new(*cx, &mut rooted); self.post_message_impl(cx, message, guard) } /// fn Start(&self) { if self.detached.get() { return; } self.global().start_message_port(self.message_port_id()); } /// fn Close(&self) { if self.detached.get() { return; } self.detached.set(true); self.global().close_message_port(self.message_port_id()); } /// fn GetOnmessage(&self) -> Option> { if self.detached.get() { return None; } let eventtarget = self.upcast::(); eventtarget.get_event_handler_common("message") } /// fn SetOnmessage(&self, listener: Option>) { 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()); } // event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror); }