use crate::store::JsValueStore;
use boa_engine::realm::Realm;
use boa_engine::value::TryFromJs;
use boa_engine::{
Context, Finalize, JsData, JsResult, JsString, JsValue, NativeObject, Trace, boa_module,
js_error,
};
use std::rc::Rc;
#[cfg(test)]
mod tests;
pub mod senders;
pub trait MessageSender: NativeObject {
fn send(&self, message: JsValueStore, target_origin: Option<JsString>) -> JsResult<()>;
fn stop(&self) -> JsResult<()> {
Ok(())
}
}
#[derive(Debug, Trace, Finalize, JsData)]
struct MessageSenderRc<T: MessageSender>(#[unsafe_ignore_trace] Rc<T>);
impl<T: MessageSender> Clone for MessageSenderRc<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
#[derive(Debug, Default, TryFromJs, Trace, Finalize)]
pub struct PostMessageOptions {
transfer: Option<Vec<JsValue>>,
target_origin: Option<JsString>,
}
fn get_sender<T: MessageSender>(context: &mut Context) -> JsResult<Rc<T>> {
if let Some(sender) = context
.get_data::<MessageSenderRc<T>>()
.cloned()
.or_else(|| {
context
.realm()
.host_defined()
.get::<MessageSenderRc<T>>()
.cloned()
})
{
Ok(sender.0.clone())
} else {
Err(
js_error!(Error: "Implementation of postMessage requires a sender registered in the context"),
)
}
}
#[boa_module]
pub mod js_module {
use crate::message::{MessageSender, PostMessageOptions, get_sender};
use crate::store::JsValueStore;
use boa_engine::value::TryFromJs;
use boa_engine::{Context, JsValue};
use boa_engine::{JsResult, js_error};
#[allow(clippy::needless_pass_by_value)]
pub fn post_message<T: MessageSender>(
message: JsValue,
target_origin_or_options: Option<JsValue>,
transfer: Option<Vec<JsValue>>,
context: &mut Context,
) -> JsResult<()> {
let (target_origin, transfer) = if let Some(target_origin_or_options) =
target_origin_or_options
{
if let Ok(options) = PostMessageOptions::try_from_js(&target_origin_or_options, context)
{
(options.target_origin.clone(), options.transfer.clone())
} else if let Some(target_origin) = target_origin_or_options.as_string() {
(Some(target_origin), transfer)
} else {
return Err(js_error!(TypeError: "targetOrigin must be a string or option object"));
}
} else {
(None, None)
};
let message = JsValueStore::try_from_js(&message, context, transfer.unwrap_or_default())?;
let sender = get_sender::<T>(context)?;
sender.send(message, target_origin)
}
}
#[doc(inline)]
pub use js_module::post_message;
pub fn register<S: MessageSender>(
sender: S,
realm: Option<Realm>,
context: &mut Context,
) -> JsResult<()> {
if let Some(ref realm) = realm {
realm
.host_defined_mut()
.insert(MessageSenderRc(Rc::new(sender)));
} else {
context.insert_data(MessageSenderRc(Rc::new(sender)));
}
js_module::boa_register::<S>(realm, context)?;
Ok(())
}