Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebIDL: Use Uint8ClampedArray instead of raw JSObject in bindings #31317

2 changes: 2 additions & 0 deletions components/script/dom/bindings/codegen/CodegenRust.py
Expand Up @@ -129,6 +129,7 @@ def wrapInNativeContainerType(type, inner):
IDLType.Tags.float32array: 'Float32Array',
IDLType.Tags.float64array: 'Float64Array',
IDLType.Tags.arrayBuffer: 'ArrayBuffer',
IDLType.Tags.uint8clampedarray: 'Uint8ClampedArray',
Taym95 marked this conversation as resolved.
Show resolved Hide resolved
}

numericTags = [
Expand Down Expand Up @@ -6518,6 +6519,7 @@ def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries
'js::typedarray::Float32Array',
'js::typedarray::Float64Array',
'js::typedarray::ArrayBuffer',
'js::typedarray::Uint8ClampedArray',
'crate::dom',
'crate::dom::bindings',
'crate::dom::bindings::codegen::InterfaceObjectMap',
Expand Down
51 changes: 51 additions & 0 deletions components/script/dom/bindings/typedarrays.rs
Expand Up @@ -31,6 +31,40 @@ unsafe impl<T> crate::dom::bindings::trace::JSTraceable for HeapTypedArray<T> {
}
}

pub fn new_initialized_heap_typed_array<T>(
init: HeapTypedArrayInit,
) -> Result<HeapTypedArray<T>, ()>
where
T: TypedArrayElement + TypedArrayElementCreator,
T::Element: Clone + Copy,
{
match init {
HeapTypedArrayInit::Object(js_object) => Ok(HeapTypedArray {
Taym95 marked this conversation as resolved.
Show resolved Hide resolved
internal: Heap::boxed(js_object),
phantom: PhantomData::default(),
}),
HeapTypedArrayInit::Info { len, cx } => {
rooted!(in (*cx) let mut array = ptr::null_mut::<JSObject>());
let typed_array_result =
create_typed_array_with_length::<T>(cx, len as usize, array.handle_mut());

match typed_array_result {
Ok(_) => {
let heap_typed_array = HeapTypedArray::<T>::default();
heap_typed_array.internal.set(*array);
Ok(heap_typed_array)
},
Err(_) => Err(()),
}
},
}
}

pub enum HeapTypedArrayInit {
Object(*mut JSObject),
Info { len: u32, cx: JSContext },
}

impl<T> HeapTypedArray<T>
where
T: TypedArrayElement + TypedArrayElementCreator,
Expand Down Expand Up @@ -161,6 +195,23 @@ where
}
}

fn create_typed_array_with_length<T>(
cx: JSContext,
len: usize,
dest: MutableHandleObject,
) -> Result<TypedArray<T, *mut JSObject>, ()>
where
T: TypedArrayElement + TypedArrayElementCreator,
{
let res = unsafe { TypedArray::<T, *mut JSObject>::create(*cx, CreateWith::Length(len), dest) };

if res.is_err() {
Err(())
} else {
TypedArray::from(dest.get())
}
}

pub fn create_new_external_array_buffer<T>(
cx: JSContext,
mapping: Arc<Mutex<Vec<T::Element>>>,
Expand Down
100 changes: 55 additions & 45 deletions components/script/dom/imagedata.rs
Expand Up @@ -3,18 +3,19 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use std::borrow::Cow;
use std::default::Default;
use std::ptr;
use std::ptr::NonNull;
use std::vec::Vec;

use dom_struct::dom_struct;
use euclid::default::{Rect, Size2D};
use ipc_channel::ipc::IpcSharedMemory;
use js::jsapi::{Heap, JSObject};
use js::rust::{HandleObject, Runtime};
use js::typedarray::{CreateWith, Uint8ClampedArray};
use js::jsapi::JSObject;
use js::rust::HandleObject;
use js::typedarray::{ClampedU8, CreateWith, Uint8ClampedArray};

use super::bindings::typedarrays::{
new_initialized_heap_typed_array, HeapTypedArray, HeapTypedArrayInit,
};
use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::ImageDataMethods;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector};
Expand All @@ -28,7 +29,7 @@ pub struct ImageData {
width: u32,
height: u32,
#[ignore_malloc_size_of = "mozjs"]
data: Heap<*mut JSObject>,
data: HeapTypedArray<ClampedU8>,
}

impl ImageData {
Expand All @@ -55,21 +56,30 @@ impl ImageData {
}

#[allow(unsafe_code)]
unsafe fn new_with_jsobject(
fn new_with_jsobject(
global: &GlobalScope,
proto: Option<HandleObject>,
width: u32,
opt_height: Option<u32>,
jsobject: *mut JSObject,
) -> Fallible<DomRoot<ImageData>> {
// checking jsobject type
let cx = GlobalScope::get_cx();
typedarray!(in(*cx) let array_res: Uint8ClampedArray = jsobject);
let array = array_res.map_err(|_| {
Error::Type("Argument to Image data is not an Uint8ClampedArray".to_owned())
})?;

let byte_len = array.as_slice().len() as u32;
let heap_typed_array = match new_initialized_heap_typed_array::<ClampedU8>(
HeapTypedArrayInit::Object(jsobject),
) {
Ok(heap_typed_array) => heap_typed_array,
Err(_) => return Err(Error::JSFailed),
};

let typed_array = match heap_typed_array.get_internal() {
Ok(array) => array,
Err(_) => {
return Err(Error::Type(
"Argument to Image data is not an Uint8ClampedArray".to_owned(),
))
},
};

let byte_len = unsafe { typed_array.as_slice().len() } as u32;
if byte_len == 0 || byte_len % 4 != 0 {
return Err(Error::InvalidState);
}
Expand All @@ -88,16 +98,13 @@ impl ImageData {
reflector_: Reflector::new(),
width: width,
height: height,
data: Heap::default(),
data: heap_typed_array,
});

(*imagedata).data.set(jsobject);

Ok(reflect_dom_object_with_proto(imagedata, global, proto))
}

#[allow(unsafe_code)]
unsafe fn new_without_jsobject(
fn new_without_jsobject(
global: &GlobalScope,
proto: Option<HandleObject>,
width: u32,
Expand All @@ -106,37 +113,40 @@ impl ImageData {
if width == 0 || height == 0 {
return Err(Error::IndexSize);
}
let cx = GlobalScope::get_cx();
let len = width * height * 4;

let heap_typed_array =
match new_initialized_heap_typed_array::<ClampedU8>(HeapTypedArrayInit::Info {
len,
cx,
}) {
Ok(heap_typed_array) => heap_typed_array,
Err(_) => return Err(Error::JSFailed),
};
let imagedata = Box::new(ImageData {
reflector_: Reflector::new(),
width: width,
height: height,
data: Heap::default(),
data: heap_typed_array,
});

let len = width * height * 4;
let cx = GlobalScope::get_cx();
rooted!(in (*cx) let mut array = ptr::null_mut::<JSObject>());
Uint8ClampedArray::create(*cx, CreateWith::Length(len as usize), array.handle_mut())
.unwrap();
(*imagedata).data.set(array.get());

Ok(reflect_dom_object_with_proto(imagedata, global, proto))
}
// https://html.spec.whatwg.org/multipage/#pixel-manipulation:dom-imagedata-3
#[allow(unsafe_code, non_snake_case)]
/// <https://html.spec.whatwg.org/multipage/#pixel-manipulation:dom-imagedata-3>
#[allow(non_snake_case)]
pub fn Constructor(
global: &GlobalScope,
proto: Option<HandleObject>,
width: u32,
height: u32,
) -> Fallible<DomRoot<Self>> {
unsafe { Self::new_without_jsobject(global, proto, width, height) }
Self::new_without_jsobject(global, proto, width, height)
}

// https://html.spec.whatwg.org/multipage/#pixel-manipulation:dom-imagedata-4
#[allow(unsafe_code, unused_variables, non_snake_case)]
pub unsafe fn Constructor_(
/// <https://html.spec.whatwg.org/multipage/#pixel-manipulation:dom-imagedata-4>
#[allow(unused_variables, non_snake_case)]
pub fn Constructor_(
cx: JSContext,
global: &GlobalScope,
proto: Option<HandleObject>,
Expand All @@ -150,17 +160,17 @@ impl ImageData {
/// Nothing must change the array on the JS side while the slice is live.
#[allow(unsafe_code)]
pub unsafe fn as_slice(&self) -> &[u8] {
assert!(!self.data.get().is_null());
let cx = Runtime::get();
assert!(!cx.is_null());
typedarray!(in(cx) let array: Uint8ClampedArray = self.data.get());
let array = array.as_ref().unwrap();
assert!(self.data.is_initialized());
let internal_data = self
.data
.get_internal()
.expect("Failed to get Data from ImageData.");
// NOTE(nox): This is just as unsafe as `as_slice` itself even though we
// are extending the lifetime of the slice, because the data in
// this ImageData instance will never change. The method is thus unsafe
// because the array may be manipulated from JS while the reference
// is live.
let ptr = array.as_slice() as *const _;
let ptr: *const [u8] = internal_data.as_slice() as *const _;
&*ptr
}

Expand All @@ -180,18 +190,18 @@ impl ImageData {
}

impl ImageDataMethods for ImageData {
// https://html.spec.whatwg.org/multipage/#dom-imagedata-width
/// <https://html.spec.whatwg.org/multipage/#dom-imagedata-width>
fn Width(&self) -> u32 {
self.width
}

// https://html.spec.whatwg.org/multipage/#dom-imagedata-height
/// <https://html.spec.whatwg.org/multipage/#dom-imagedata-height>
fn Height(&self) -> u32 {
self.height
}

// https://html.spec.whatwg.org/multipage/#dom-imagedata-data
fn Data(&self, _: JSContext) -> NonNull<JSObject> {
NonNull::new(self.data.get()).expect("got a null pointer")
/// <https://html.spec.whatwg.org/multipage/#dom-imagedata-data>
fn GetData(&self, _: JSContext) -> Fallible<Uint8ClampedArray> {
self.data.get_internal().map_err(|_| Error::JSFailed)
}
}
19 changes: 10 additions & 9 deletions components/script/dom/testbinding.rs
Expand Up @@ -5,14 +5,14 @@
// check-tidy: no specs after this line

use std::borrow::ToOwned;
use std::ptr::NonNull;
use std::ptr::{self, NonNull};
use std::rc::Rc;

use dom_struct::dom_struct;
use js::jsapi::{Heap, JSObject, JS_NewPlainObject, JS_NewUint8ClampedArray};
use js::jsapi::{Heap, JSObject, JS_NewPlainObject};
use js::jsval::{JSVal, NullValue};
use js::rust::{CustomAutoRooterGuard, HandleObject, HandleValue};
use js::typedarray;
use js::typedarray::{self, Uint8ClampedArray};
use script_traits::serializable::BlobImpl;
use script_traits::MsDuration;
use servo_config::prefs;
Expand Down Expand Up @@ -41,6 +41,7 @@ use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject,
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::{ByteString, DOMString, USVString};
use crate::dom::bindings::trace::RootedTraceableBox;
use crate::dom::bindings::typedarrays::create_typed_array;
use crate::dom::bindings::weakref::MutableWeakRef;
use crate::dom::blob::Blob;
use crate::dom::globalscope::GlobalScope;
Expand Down Expand Up @@ -209,12 +210,12 @@ impl TestBindingMethods for TestBinding {
ByteStringOrLong::ByteString(ByteString::new(vec![]))
}
fn SetUnion9Attribute(&self, _: ByteStringOrLong) {}
#[allow(unsafe_code)]
fn ArrayAttribute(&self, cx: SafeJSContext) -> NonNull<JSObject> {
unsafe {
rooted!(in(*cx) let array = JS_NewUint8ClampedArray(*cx, 16));
NonNull::new(array.get()).expect("got a null pointer")
}
fn ArrayAttribute(&self, cx: SafeJSContext) -> Uint8ClampedArray {
let data: [u8; 16] = [0; 16];

rooted!(in (*cx) let mut array = ptr::null_mut::<JSObject>());
create_typed_array(cx, &data, array.handle_mut())
.expect("Creating ClampedU8 array should never fail")
}
fn AnyAttribute(&self, _: SafeJSContext) -> JSVal {
NullValue()
Expand Down
Expand Up @@ -259,6 +259,6 @@ interface ImageData {

readonly attribute unsigned long width;
readonly attribute unsigned long height;
readonly attribute Uint8ClampedArray data;
[Throws] readonly attribute Uint8ClampedArray data;
//readonly attribute PredefinedColorSpace colorSpace;
};
3 changes: 2 additions & 1 deletion third_party/WebIDL/WebIDL.py
Expand Up @@ -2410,6 +2410,7 @@ class IDLType(IDLObject):
"float32array",
"float64array",
"arrayBuffer",
"uint8clampedarray",
"dictionary",
"enum",
"callback",
Expand Down Expand Up @@ -3644,7 +3645,7 @@ class IDLBuiltinType(IDLType):
Types.ArrayBufferView: IDLType.Tags.interface,
Types.Int8Array: IDLType.Tags.int8array,
Types.Uint8Array: IDLType.Tags.uint8array,
Types.Uint8ClampedArray: IDLType.Tags.interface,
Types.Uint8ClampedArray: IDLType.Tags.uint8clampedarray,
Types.Int16Array: IDLType.Tags.int16array,
Types.Uint16Array: IDLType.Tags.uint16array,
Types.Int32Array: IDLType.Tags.int32array,
Expand Down