runtime/globals/streams/
strategy.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use 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}