Skip to main content

blitz_script/
media.rs

1//! Device-free HTMLMediaElement load requests for the embedding media host.
2
3use blitz_dom::NodeId;
4use boa_engine::object::JsObject;
5use boa_engine::{Context, Finalize, JsData, JsNativeError, JsResult, JsValue, Trace};
6use boa_gc::GcRefCell;
7
8use crate::dom::{define_method, dom_ctx, this_node_id};
9
10/// The wrapper keeps the node alive until the host consumes the request.
11#[derive(Trace, Finalize, JsData)]
12pub struct MediaLoadRequest {
13    #[unsafe_ignore_trace]
14    pub node_id: NodeId,
15    pub element: JsObject,
16}
17
18#[derive(Trace, Finalize, JsData)]
19struct MediaLoads {
20    pending: GcRefCell<Vec<MediaLoadRequest>>,
21}
22
23pub(crate) fn install(proto: &JsObject, context: &mut Context) {
24    context.insert_data(MediaLoads {
25        pending: GcRefCell::new(Vec::new()),
26    });
27    define_method(proto, "load", 0, load, context);
28}
29
30fn load(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
31    let node_id = this_node_id(this)?;
32    let ctx = dom_ctx(context)?;
33    let valid = ctx.doc.borrow().get_node(node_id).is_some_and(|node| {
34        node.element_data().is_some_and(|element| {
35            element.name.ns.as_ref() == "http://www.w3.org/1999/xhtml"
36                && matches!(element.name.local.as_ref(), "audio" | "video")
37        })
38    });
39    if !valid {
40        return Err(JsNativeError::typ()
41            .with_message("Illegal HTMLMediaElement receiver")
42            .into());
43    }
44    let element = this.as_object().expect("validated media wrapper");
45    {
46        let loads = context
47            .get_data::<MediaLoads>()
48            .expect("media loads installed");
49        let mut pending = loads.pending.borrow_mut();
50        pending.retain(|request| request.node_id != node_id);
51        pending.push(MediaLoadRequest { node_id, element });
52    }
53    ctx.doc.borrow().shell_provider.request_redraw();
54    Ok(JsValue::undefined())
55}
56
57/// Drain from `ScriptDocument::with_js_context` in the embedding host's poll hook.
58///
59/// The host must abort the element's previous media load and run source
60/// selection. This layer queues requests only and never opens an audio device.
61pub fn take_load_requests(context: &mut Context) -> Vec<MediaLoadRequest> {
62    let Some(loads) = context.get_data::<MediaLoads>() else {
63        return Vec::new();
64    };
65    std::mem::take(&mut *loads.pending.borrow_mut())
66}