1use std::any::TypeId;
8use std::slice;
9
10use ion::class::Reflector;
11use ion::conversions::FromValue;
12use ion::flags::PropertyFlags;
13use ion::{
14 ClassDefinition as _, Context, FromValue, Function, Local, Object, Result, ResultExc, Traceable, Value, js_class,
15 js_fn,
16};
17use mozjs::jsapi::{CallArgs, Heap, JS_GetFunctionObject, JS_GetObjectFunction, JSContext, JSFunction};
18use mozjs::jsval::{Int32Value, JSVal, ObjectValue, UndefinedValue};
19
20use crate::ContextExt as _;
21
22#[derive(Debug, Traceable)]
23pub enum SizeCallback {
24 None,
25 Script(Box<Heap<*mut JSFunction>>),
26 ByteLength,
27 Count,
28}
29
30impl SizeCallback {
31 pub(crate) fn compute<'cx>(&self, cx: &'cx Context, chunk: &Value) -> ResultExc<Value<'cx>> {
32 match self {
33 SizeCallback::None | SizeCallback::Count => Ok(Value::i32(cx, 1)),
34 SizeCallback::Script(cb) => {
35 let cb = Function::from(unsafe { Local::from_heap(cb) });
36 cb.call(cx, &Object::null(cx), slice::from_ref(chunk))
37 .map_err(|report| report.unwrap().exception)
38 }
39 SizeCallback::ByteLength => {
40 let chunk = Object::from_value(cx, chunk, true, ())?;
41 Ok(chunk.get(cx, "byteLength")?.unwrap_or_else(Value::undefined_handle))
42 }
43 }
44 }
45}
46
47#[derive(Debug)]
48pub enum QueuingStrategy<'cx> {
49 Script(ScriptQueuingStrategy<'cx>),
50 ByteLength(f64),
51 Count(f64),
52}
53
54impl QueuingStrategy<'_> {
55 pub(crate) fn high_water_mark(&self) -> Option<f64> {
56 match self {
57 QueuingStrategy::Script(strategy) => strategy.high_water_mark,
58 QueuingStrategy::ByteLength(high_water_mark) | QueuingStrategy::Count(high_water_mark) => {
59 Some(*high_water_mark)
60 }
61 }
62 }
63
64 pub(crate) fn has_size(&self) -> bool {
65 match self {
66 QueuingStrategy::Script(strategy) => strategy.size.is_some(),
67 QueuingStrategy::ByteLength(_) | QueuingStrategy::Count(_) => true,
68 }
69 }
70
71 pub(crate) fn size(&self) -> SizeCallback {
72 match self {
73 QueuingStrategy::Script(strategy) => strategy.size.as_ref().map_or(SizeCallback::None, |strategy| {
74 SizeCallback::Script(Heap::boxed(strategy.get()))
75 }),
76 QueuingStrategy::ByteLength(_) => SizeCallback::ByteLength,
77 QueuingStrategy::Count(_) => SizeCallback::Count,
78 }
79 }
80}
81
82impl<'cx> Default for QueuingStrategy<'cx> {
83 fn default() -> QueuingStrategy<'cx> {
84 QueuingStrategy::Script(ScriptQueuingStrategy::default())
85 }
86}
87
88impl<'cx> FromValue<'cx> for QueuingStrategy<'cx> {
89 type Config = ();
90
91 fn from_value(cx: &'cx Context, value: &Value, strict: bool, _: ()) -> Result<QueuingStrategy<'cx>> {
92 let object = Object::from_value(cx, value, strict, ())?;
93 if let Ok(byte_length) = ByteLengthQueuingStrategy::get_private(cx, &object) {
94 return Ok(QueuingStrategy::ByteLength(byte_length.high_water_mark));
95 }
96
97 ScriptQueuingStrategy::from_value(cx, value, strict, ()).map(QueuingStrategy::Script)
98 }
99}
100
101#[derive(Debug, Default, FromValue)]
102pub struct ScriptQueuingStrategy<'cx> {
103 high_water_mark: Option<f64>,
104 size: Option<Function<'cx>>,
105}
106
107#[derive(Debug, FromValue)]
108pub struct QueuingStrategyInit {
109 high_water_mark: f64,
110}
111
112#[js_fn]
113fn byte_length_size(cx: &Context, object: Object) -> Result<JSVal> {
114 Ok(object.get_as(cx, "byteLength", false, ())?.unwrap_or_else(UndefinedValue))
115}
116
117#[js_class]
118pub(crate) struct ByteLengthQueuingStrategy {
119 reflector: Reflector,
120 high_water_mark: f64,
121}
122
123#[js_class]
124impl ByteLengthQueuingStrategy {
125 #[ion(constructor)]
126 pub fn constructor(cx: &Context, options: QueuingStrategyInit) -> ByteLengthQueuingStrategy {
127 let key = TypeId::of::<ByteLengthQueuingStrategy>();
128 unsafe {
129 cx.get_private().globals.entry(key).or_insert_with(|| {
130 let size = Function::new(cx, "size", Some(byte_length_size), 1, PropertyFlags::empty());
131 Heap::boxed(ObjectValue(JS_GetFunctionObject(size.get())))
132 });
133 }
134
135 ByteLengthQueuingStrategy {
136 reflector: Reflector::default(),
137 high_water_mark: options.high_water_mark,
138 }
139 }
140
141 #[ion(get)]
142 pub fn get_high_water_mark(&self) -> f64 {
143 self.high_water_mark
144 }
145
146 #[ion(get)]
147 #[expect(clippy::unused_self)]
148 pub fn get_size(&self, cx: &Context) -> *mut JSFunction {
149 let key = TypeId::of::<ByteLengthQueuingStrategy>();
150 unsafe { JS_GetObjectFunction(cx.get_private().globals.get(&key).unwrap().get().to_object()) }
151 }
152}
153
154unsafe extern "C" fn count_size(_cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
155 unsafe {
156 CallArgs::from_vp(vp, argc).rval().set(Int32Value(1));
157 }
158 true
159}
160
161#[js_class]
162pub(crate) struct CountQueuingStrategy {
163 reflector: Reflector,
164 high_water_mark: f64,
165}
166
167#[js_class]
168impl CountQueuingStrategy {
169 #[ion(constructor)]
170 pub fn constructor(cx: &Context, options: QueuingStrategyInit) -> CountQueuingStrategy {
171 let key = TypeId::of::<CountQueuingStrategy>();
172 unsafe {
173 cx.get_private().globals.entry(key).or_insert_with(|| {
174 let size = Function::new(cx, "size", Some(count_size), 0, PropertyFlags::empty());
175 Heap::boxed(ObjectValue(JS_GetFunctionObject(size.get())))
176 });
177 }
178
179 CountQueuingStrategy {
180 reflector: Reflector::default(),
181 high_water_mark: options.high_water_mark,
182 }
183 }
184
185 #[ion(get)]
186 pub fn get_high_water_mark(&self) -> f64 {
187 self.high_water_mark
188 }
189
190 #[ion(get)]
191 #[expect(clippy::unused_self)]
192 pub fn get_size(&self, cx: &Context) -> *mut JSFunction {
193 let key = TypeId::of::<CountQueuingStrategy>();
194 unsafe { JS_GetObjectFunction(cx.get_private().globals.get(&key).unwrap().get().to_object()) }
195 }
196}