Skip to content

Commit ab2f80b

Browse files
committed
feat: Introduce the JSTypedArray type.
This patch introduces the `JSTypedArray` type. It holds the `ty` method (which replaces `JSValue::get_typed_array_type`), `len`, `byte_offset`, `byte_length` and `as_mut_slice`.
1 parent f83e891 commit ab2f80b

File tree

3 files changed

+417
-27
lines changed

3 files changed

+417
-27
lines changed

Diff for: src/lib.rs

+13
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ mod contextgroup;
3030
mod exception;
3131
mod object;
3232
mod string;
33+
mod typed_array;
3334
mod value;
3435

3536
pub use crate::sys::{JSType, JSTypedArrayType};
@@ -122,6 +123,17 @@ pub struct JSString {
122123
raw: sys::JSStringRef,
123124
}
124125

126+
/// A JavaScript Typed Array.
127+
///
128+
/// A Typed Array is a special JavaScript object that represent a family of
129+
/// buffer view. Learn more by [reading the documentation][doc].
130+
///
131+
/// [doc]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray#behavior_when_viewing_a_resizable_buffer
132+
pub struct JSTypedArray {
133+
raw: sys::JSObjectRef,
134+
ctx: sys::JSContextRef,
135+
}
136+
125137
/// A JavaScript value.
126138
///
127139
/// The base type for all JavaScript values, and polymorphic functions
@@ -152,6 +164,7 @@ pub struct JSString {
152164
/// * [`JSValue::as_number()`]
153165
/// * [`JSValue::as_object()`]
154166
/// * [`JSValue::as_string()`]
167+
/// * [`JSValue::as_typed_array()`]
155168
#[derive(Debug)]
156169
pub struct JSValue {
157170
raw: sys::JSValueRef,

Diff for: src/typed_array.rs

+360
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
2+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
3+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
4+
// option. This file may not be copied, modified, or distributed
5+
// except according to those terms.
6+
7+
use crate::{sys, JSException, JSObject, JSTypedArray, JSTypedArrayType, JSValue};
8+
use std::{ptr, slice};
9+
10+
impl JSTypedArray {
11+
/// Create a new [`Self`] from its raw pointer directly.
12+
///
13+
/// # Safety
14+
///
15+
/// Ensure `raw` is valid.
16+
pub(crate) unsafe fn from_raw(ctx: sys::JSContextRef, raw: sys::JSObjectRef) -> Self {
17+
Self { ctx, raw }
18+
}
19+
20+
/// Returns a value of type [`JSTypedArrayType`] that identifies value's
21+
/// Typed Array type, or `JSTypedArrayType::None` if the value is not a Typed Array
22+
/// object.
23+
///
24+
/// ```rust
25+
/// # use javascriptcore::*;
26+
/// let ctx = JSContext::default();
27+
/// let array = evaluate_script(
28+
/// &ctx,
29+
/// "new Uint8Array([1, 2, 3, 4, 5])",
30+
/// None,
31+
/// "foo.js",
32+
/// 1,
33+
/// )
34+
/// .unwrap()
35+
/// .as_typed_array()
36+
/// .unwrap();
37+
/// assert_eq!(array.ty().unwrap(), JSTypedArrayType::Uint8Array);
38+
/// ```
39+
pub fn ty(&self) -> Result<JSTypedArrayType, JSException> {
40+
let mut exception: sys::JSValueRef = ptr::null_mut();
41+
let value = unsafe { sys::JSValueGetTypedArrayType(self.ctx, self.raw, &mut exception) };
42+
43+
if !exception.is_null() {
44+
Err(unsafe { JSValue::from_raw(self.ctx, exception) }.into())
45+
} else {
46+
Ok(value)
47+
}
48+
}
49+
50+
/// Returns the length of the Typed Array.
51+
///
52+
/// ```rust
53+
/// # use javascriptcore::*;
54+
/// let ctx = JSContext::default();
55+
/// let array = evaluate_script(
56+
/// &ctx,
57+
/// "new Uint8Array([1, 2, 3, 4, 5])",
58+
/// None,
59+
/// "foo.js",
60+
/// 1,
61+
/// )
62+
/// .unwrap()
63+
/// .as_typed_array()
64+
/// .unwrap();
65+
/// assert_eq!(array.len().unwrap(), 5);
66+
/// ```
67+
#[allow(clippy::len_without_is_empty)]
68+
pub fn len(&self) -> Result<usize, JSException> {
69+
let mut exception: sys::JSValueRef = ptr::null_mut();
70+
let value = unsafe { sys::JSObjectGetTypedArrayLength(self.ctx, self.raw, &mut exception) };
71+
72+
if !exception.is_null() {
73+
Err(unsafe { JSValue::from_raw(self.ctx, exception) }.into())
74+
} else {
75+
Ok(value)
76+
}
77+
}
78+
79+
/// Returns the byte offset of the Typed Array.
80+
///
81+
/// ```rust
82+
/// # use javascriptcore::*;
83+
/// let ctx = JSContext::default();
84+
/// let array = evaluate_script(
85+
/// &ctx,
86+
/// "const array = new Uint8Array([1, 2, 3, 4, 5]); new Uint8Array(array.buffer, 3)",
87+
/// None,
88+
/// "foo.js",
89+
/// 1,
90+
/// )
91+
/// .unwrap()
92+
/// .as_typed_array()
93+
/// .unwrap();
94+
/// assert_eq!(array.byte_offset().unwrap(), 3);
95+
/// ```
96+
pub fn byte_offset(&self) -> Result<usize, JSException> {
97+
let mut exception: sys::JSValueRef = ptr::null_mut();
98+
let offset =
99+
unsafe { sys::JSObjectGetTypedArrayByteOffset(self.ctx, self.raw, &mut exception) };
100+
101+
if !exception.is_null() {
102+
Err(unsafe { JSValue::from_raw(self.ctx, exception) }.into())
103+
} else {
104+
Ok(offset)
105+
}
106+
}
107+
108+
/// Returns the byte length of the Typed Array.
109+
/// ```rust
110+
/// # use javascriptcore::*;
111+
/// let ctx = JSContext::default();
112+
/// let array = evaluate_script(
113+
/// &ctx,
114+
/// "const array = new Uint8Array([1, 2, 3, 4, 5]); new Uint8Array(array.buffer, 1, 2)",
115+
/// None,
116+
/// "foo.js",
117+
/// 1,
118+
/// )
119+
/// .unwrap()
120+
/// .as_typed_array()
121+
/// .unwrap();
122+
/// assert_eq!(array.byte_length().unwrap(), 2);
123+
/// ```
124+
pub fn byte_length(&self) -> Result<usize, JSException> {
125+
let mut exception: sys::JSValueRef = ptr::null_mut();
126+
let length =
127+
unsafe { sys::JSObjectGetTypedArrayByteLength(self.ctx, self.raw, &mut exception) };
128+
129+
if !exception.is_null() {
130+
Err(unsafe { JSValue::from_raw(self.ctx, exception) }.into())
131+
} else {
132+
Ok(length)
133+
}
134+
}
135+
136+
/// Returns a mutable slice of the underlying buffer represented by the
137+
/// Typed Array.
138+
///
139+
/// # Safety
140+
///
141+
/// The pointer of the slice returned by this function is temporary and is not
142+
/// guaranteed to remain valid across JavaScriptCore API calls.
143+
pub unsafe fn as_mut_slice(&mut self) -> Result<&mut [u8], JSException> {
144+
let offset = self.byte_offset()?;
145+
let length = self.len()?;
146+
147+
let mut exception: sys::JSValueRef = ptr::null_mut();
148+
let ptr = sys::JSObjectGetTypedArrayBytesPtr(self.ctx, self.raw, &mut exception);
149+
150+
if !exception.is_null() {
151+
Err(JSValue::from_raw(self.ctx, exception).into())
152+
} else {
153+
assert!(!ptr.is_null(), "`ptr` must not be null");
154+
155+
Ok(slice::from_raw_parts_mut(
156+
ptr.offset(offset.try_into().unwrap()) as *mut u8,
157+
length,
158+
))
159+
}
160+
}
161+
}
162+
163+
impl From<&JSTypedArray> for JSObject {
164+
fn from(array: &JSTypedArray) -> Self {
165+
// SAFETY: `ctx` and `raw` is valid, it's safe to use them.
166+
unsafe { JSObject::from_raw(array.ctx, array.raw) }
167+
}
168+
}
169+
170+
impl From<JSTypedArray> for JSObject {
171+
fn from(array: JSTypedArray) -> Self {
172+
(&array).into()
173+
}
174+
}
175+
176+
#[cfg(test)]
177+
mod tests {
178+
use super::*;
179+
use crate::{evaluate_script, JSContext};
180+
181+
#[test]
182+
fn new() -> Result<(), JSException> {
183+
let ctx = JSContext::default();
184+
let mut bytes = vec![1u8, 2, 3, 4, 5];
185+
let array = unsafe { JSValue::new_typed_array_with_bytes(&ctx, bytes.as_mut_slice()) }?;
186+
187+
// It's a Typed Array.
188+
assert!(array.is_typed_array());
189+
190+
let array = array.as_typed_array()?;
191+
// It's a `Uint8Array`.
192+
assert_eq!(array.ty()?, JSTypedArrayType::Uint8Array);
193+
194+
// Can go to `JSObject` and `JSValue` again.
195+
assert!(JSValue::from(JSObject::from(array)).is_typed_array());
196+
197+
Ok(())
198+
}
199+
200+
#[test]
201+
fn len() -> Result<(), JSException> {
202+
let ctx = JSContext::default();
203+
let mut bytes = vec![1u8, 2, 3, 4, 5];
204+
let array = unsafe { JSValue::new_typed_array_with_bytes(&ctx, bytes.as_mut_slice()) }?
205+
.as_typed_array()?;
206+
207+
assert_eq!(array.len()?, 5);
208+
209+
Ok(())
210+
}
211+
212+
#[test]
213+
fn byte_offset() -> Result<(), JSException> {
214+
let ctx = JSContext::default();
215+
let mut bytes = vec![1u8, 2, 3, 4, 5];
216+
let array = unsafe { JSValue::new_typed_array_with_bytes(&ctx, bytes.as_mut_slice()) }?
217+
.as_typed_array()?;
218+
219+
assert_eq!(array.byte_offset()?, 0);
220+
221+
// More complex.
222+
let array = evaluate_script(
223+
&ctx,
224+
"const array = new Uint8Array([1, 2, 3, 4, 5]); new Uint8Array(array.buffer, 2)",
225+
None,
226+
"foo.js",
227+
1,
228+
)?
229+
.as_typed_array()?;
230+
231+
assert_eq!(array.len()?, 3);
232+
assert_eq!(array.byte_offset()?, 2);
233+
234+
Ok(())
235+
}
236+
237+
#[test]
238+
fn byte_length() -> Result<(), JSException> {
239+
let ctx = JSContext::default();
240+
let mut bytes = vec![1u8, 2, 3, 4, 5];
241+
let array = unsafe { JSValue::new_typed_array_with_bytes(&ctx, bytes.as_mut_slice()) }?
242+
.as_typed_array()?;
243+
244+
assert_eq!(array.byte_length()?, 5);
245+
246+
// More complex.
247+
let array = evaluate_script(
248+
&ctx,
249+
"const array = new Uint8Array([1, 2, 3, 4, 5]); new Uint8Array(array.buffer, 2, 2)",
250+
None,
251+
"foo.js",
252+
1,
253+
)?
254+
.as_typed_array()?;
255+
256+
assert_eq!(array.len()?, 2);
257+
assert_eq!(array.byte_length()?, 2);
258+
259+
Ok(())
260+
}
261+
262+
#[test]
263+
fn as_mut_slice_has_correct_items() -> Result<(), JSException> {
264+
let ctx = JSContext::default();
265+
266+
// No byte offset, no byte length.
267+
{
268+
let mut array = evaluate_script(
269+
&ctx,
270+
"const array0 = new Uint8Array([1, 2, 3, 4, 5]); new Uint8Array(array0.buffer)",
271+
None,
272+
"foo.js",
273+
1,
274+
)?
275+
.as_typed_array()?;
276+
277+
assert_eq!(array.len()?, 5);
278+
assert_eq!(array.byte_offset()?, 0);
279+
assert_eq!(array.byte_length()?, 5);
280+
assert_eq!(unsafe { array.as_mut_slice()? }, &[1, 2, 3, 4, 5]);
281+
}
282+
283+
// A byte offset, no byte length.
284+
{
285+
let mut array = evaluate_script(
286+
&ctx,
287+
"const array1 = new Uint8Array([1, 2, 3, 4, 5]); new Uint8Array(array1.buffer, 1)",
288+
None,
289+
"foo.js",
290+
2,
291+
)?
292+
.as_typed_array()?;
293+
294+
assert_eq!(array.len()?, 4);
295+
assert_eq!(array.byte_offset()?, 1);
296+
assert_eq!(array.byte_length()?, 4);
297+
assert_eq!(unsafe { array.as_mut_slice()? }, &[2, 3, 4, 5]);
298+
}
299+
300+
// A byte offset, a byte length, the typed array is length-tracking.
301+
{
302+
let mut array = evaluate_script(
303+
&ctx,
304+
"const array2 = new Uint8Array([1, 2, 3, 4, 5]); new Uint8Array(array2.buffer, 1, 3)",
305+
None,
306+
"foo.js",
307+
3,
308+
)?
309+
.as_typed_array()?;
310+
311+
assert_eq!(array.len()?, 3);
312+
assert_eq!(array.byte_offset()?, 1);
313+
assert_eq!(array.byte_length()?, 3);
314+
assert_eq!(unsafe { array.as_mut_slice()? }, &[2, 3, 4]);
315+
}
316+
317+
Ok(())
318+
}
319+
320+
#[test]
321+
fn as_mut_slice_is_mutable() -> Result<(), JSException> {
322+
let ctx = JSContext::default();
323+
324+
let mut bytes = vec![1u8, 2, 3, 4, 5];
325+
let array_as_value =
326+
unsafe { JSValue::new_typed_array_with_bytes(&ctx, bytes.as_mut_slice()) }?;
327+
let mut array = array_as_value.as_typed_array()?;
328+
329+
ctx.global_object()?.set_property("array", array_as_value)?;
330+
331+
let mut sub_array = evaluate_script(
332+
&ctx,
333+
"new Uint8Array(array.buffer, 1, 3)",
334+
None,
335+
"foo.js",
336+
1,
337+
)?
338+
.as_typed_array()?;
339+
340+
assert_eq!(sub_array.len()?, 3);
341+
assert_eq!(sub_array.byte_offset()?, 1);
342+
assert_eq!(sub_array.byte_length()?, 3);
343+
let sub_slice = unsafe { sub_array.as_mut_slice() }?;
344+
345+
// Items are untouched.
346+
assert_eq!(sub_slice, &[2, 3, 4]);
347+
assert_eq!(bytes, &[1, 2, 3, 4, 5]);
348+
349+
// Now let's mutate them.
350+
sub_slice[0] = 12;
351+
sub_slice[2] = 14;
352+
353+
// See, they are mutated.
354+
assert_eq!(sub_slice, &[12, 3, 14]);
355+
assert_eq!(bytes, &[1, 12, 3, 14, 5]);
356+
assert_eq!(unsafe { array.as_mut_slice() }?, &[1, 12, 3, 14, 5]);
357+
358+
Ok(())
359+
}
360+
}

0 commit comments

Comments
 (0)