/* 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::num::NonZeroU32; use std::ptr; use std::rc::Rc; use base::id::{MessagePortId, MessagePortIndex, PipelineNamespaceId}; use constellation_traits::{MessagePortImpl, PortMessageTask}; use dom_struct::dom_struct; use js::jsapi::{Heap, JS_NewObject, JSObject}; use js::jsval::UndefinedValue; use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue}; use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; use crate::dom::bindings::codegen::Bindings::MessagePortBinding::{ MessagePortMethods, StructuredSerializeOptions, }; use crate::dom::bindings::conversions::root_from_object; use crate::dom::bindings::error::{Error, ErrorResult, ErrorToJsval}; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object}; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::structuredclone::{self, StructuredData, StructuredDataReader}; use crate::dom::bindings::trace::RootedTraceableBox; use crate::dom::bindings::transferable::{ExtractComponents, IdFromComponents, Transferable}; use crate::dom::bindings::utils::set_dictionary_property; use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; use crate::js::conversions::ToJSValConvertible; use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; #[dom_struct] /// The MessagePort used in the DOM. pub(crate) 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(crate) fn new(owner: &GlobalScope, can_gc: CanGc) -> DomRoot { let port_id = MessagePortId::new(); reflect_dom_object(Box::new(MessagePort::new_inherited(port_id)), owner, can_gc) } /// Create a new port for an incoming transfer-received one. pub(crate) fn new_transferred( owner: &GlobalScope, transferred_port: MessagePortId, entangled_port: Option, can_gc: CanGc, ) -> 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, can_gc, ) } /// pub(crate) fn entangle(&self, other_id: MessagePortId) { *self.entangled_port.borrow_mut() = Some(other_id); } pub(crate) fn message_port_id(&self) -> &MessagePortId { &self.message_port_id } pub(crate) fn detached(&self) -> bool { self.detached.get() } /// fn set_onmessage(&self, listener: Option>) { let eventtarget = self.upcast::(); eventtarget.set_event_handler_common("message", listener); } /// #[allow(unsafe_code)] 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| unsafe { 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(None)); } // 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(()) } /// pub(crate) fn cross_realm_transform_send_error(&self, error: HandleValue, can_gc: CanGc) { // Perform PackAndPostMessage(port, "error", error), // discarding the result. let _ = self.pack_and_post_message("error", error, can_gc); } /// #[allow(unsafe_code)] pub(crate) fn pack_and_post_message_handling_error( &self, type_: &str, value: HandleValue, can_gc: CanGc, ) -> ErrorResult { // Let result be PackAndPostMessage(port, type, value). let result = self.pack_and_post_message(type_, value, can_gc); // If result is an abrupt completion, if let Err(error) = result.as_ref() { // Perform ! CrossRealmTransformSendError(port, result.[[Value]]). let cx = GlobalScope::get_cx(); rooted!(in(*cx) let mut rooted_error = UndefinedValue()); error .clone() .to_jsval(cx, &self.global(), rooted_error.handle_mut(), can_gc); self.cross_realm_transform_send_error(rooted_error.handle(), can_gc); } result } /// #[allow(unsafe_code)] pub(crate) fn pack_and_post_message( &self, type_: &str, value: HandleValue, _can_gc: CanGc, ) -> ErrorResult { let cx = GlobalScope::get_cx(); // Let message be OrdinaryObjectCreate(null). rooted!(in(*cx) let mut message = unsafe { JS_NewObject(*cx, ptr::null()) }); rooted!(in(*cx) let mut type_string = UndefinedValue()); unsafe { type_.to_jsval(*cx, type_string.handle_mut()); } // Perform ! CreateDataProperty(message, "type", type). unsafe { set_dictionary_property(*cx, message.handle(), "type", type_string.handle()) .expect("Setting the message type should not fail."); } // Perform ! CreateDataProperty(message, "value", value). unsafe { set_dictionary_property(*cx, message.handle(), "value", value) .expect("Setting the message value should not fail."); } // Let targetPort be the port with which port is entangled, if any; otherwise let it be null. // Done in `global.post_messageport_msg`. // Let options be «[ "transfer" → « » ]». let mut rooted = CustomAutoRooter::new(vec![]); let transfer = CustomAutoRooterGuard::new(*cx, &mut rooted); // Run the message port post message steps providing targetPort, message, and options. rooted!(in(*cx) let mut message_val = UndefinedValue()); unsafe { message.to_jsval(*cx, message_val.handle_mut()); } self.post_message_impl(cx, message_val.handle(), transfer) } } impl Transferable for MessagePort { type Id = MessagePortId; type Data = MessagePortImpl; /// fn transfer(&self) -> Result<(MessagePortId, MessagePortImpl), ()> { if self.detached.get() { return Err(()); } 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); Ok((*id, transferred_port)) } /// fn transfer_receive( owner: &GlobalScope, id: MessagePortId, port_impl: MessagePortImpl, ) -> Result, ()> { let transferred_port = MessagePort::new_transferred(owner, id, port_impl.entangled_port_id(), CanGc::note()); owner.track_message_port(&transferred_port, Some(port_impl)); Ok(transferred_port) } fn serialized_storage(data: StructuredData<'_>) -> &mut Option> { match data { StructuredData::Reader(r) => &mut r.port_impls, StructuredData::Writer(w) => &mut w.ports, } } fn deserialized_storage(reader: &mut StructuredDataReader) -> &mut Option>> { &mut reader.message_ports } } impl IdFromComponents for MessagePortId { fn from(namespace_id: PipelineNamespaceId, index: NonZeroU32) -> MessagePortId { MessagePortId { namespace_id, index: MessagePortIndex(index), } } } impl ExtractComponents for MessagePortId { fn components(&self) -> (PipelineNamespaceId, NonZeroU32) { (self.namespace_id, self.index.0) } } 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, can_gc: CanGc) -> Option> { if self.detached.get() { return None; } let eventtarget = self.upcast::(); eventtarget.get_event_handler_common("message", can_gc) } /// 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); }