ArrayBufferView objects now fully supported

Previously we were not accounting for the byte offsets and misunderstood how the Uint8Array constructor worked. The tests now confirm everything works as expected with multiple different typed arrays and slices of the data.
This commit is contained in:
Scott Kyle 2015-11-16 13:48:37 -08:00
parent 7f32ab0b88
commit 7b115a934b
4 changed files with 88 additions and 33 deletions

View File

@ -43,8 +43,20 @@ function decode(base64) {
}
function encode(data) {
let bytes = new Uint8Array(data);
let byteCount = bytes.length;
var byteOffset = 0;
var buffer;
if (data instanceof ArrayBuffer) {
buffer = data;
} else if (ArrayBuffer.isView(data)) {
buffer = data.buffer;
byteOffset = data.byteOffset;
} else {
throw new TypeError('Can only base64 encode ArrayBuffer and ArrayBufferView objects');
}
let byteCount = data.byteLength;
let bytes = new Uint8Array(buffer, byteOffset, byteCount);
let base64 = '';
for (let i = 0; i < byteCount; i += 3) {
@ -59,7 +71,7 @@ function encode(data) {
return base64.slice(0, -2) + '==';
case 2:
return base64.slice(0, -1) + '=';
}
default:
return base64;
}
}

View File

@ -125,9 +125,6 @@ function serialize(realmId, value) {
}
if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
if (value instanceof DataView) {
throw new Error('Unable to serialize a DataView object');
}
return {type: propTypes.DATA, value: base64.encode(value)};
}

View File

@ -132,34 +132,41 @@ template<> JSValueRef RJSAccessor::from_string(JSContextRef ctx, StringData s) {
template<> std::string RJSAccessor::to_binary(JSContextRef ctx, JSValueRef &val) {
static JSStringRef arrayBufferString = JSStringCreateWithUTF8CString("ArrayBuffer");
static JSStringRef dataViewString = JSStringCreateWithUTF8CString("DataView");
static JSStringRef bufferString = JSStringCreateWithUTF8CString("buffer");
static JSStringRef byteLengthString = JSStringCreateWithUTF8CString("byteLength");
static JSStringRef byteOffsetString = JSStringCreateWithUTF8CString("byteOffset");
static JSStringRef isViewString = JSStringCreateWithUTF8CString("isView");
static JSStringRef uint8ArrayString = JSStringCreateWithUTF8CString("Uint8Array");
switch (JSValueGetType(ctx, val)) {
case kJSTypeObject: {
// Allow value to be an ArrayBuffer instance.
JSObjectRef arrayBufferConstructor = RJSValidatedObjectProperty(ctx, JSContextGetGlobalObject(ctx), arrayBufferString);
if (JSValueIsInstanceOfConstructor(ctx, val, arrayBufferConstructor, NULL)) {
break;
}
JSObjectRef uint8ArrayContructor = RJSValidatedObjectProperty(ctx, JSContextGetGlobalObject(ctx), uint8ArrayString);
JSValueRef uint8ArrayArguments[3];
size_t uint8ArrayArgumentsCount = 0;
// Check if value is a TypedArray by calling ArrayBuffer.isView(val), but *not* a DataView object.
// Value should either be an ArrayBuffer or ArrayBufferView (i.e. TypedArray or DataView).
if (JSValueIsInstanceOfConstructor(ctx, val, arrayBufferConstructor, NULL)) {
uint8ArrayArguments[0] = val;
uint8ArrayArgumentsCount = 1;
}
else if (JSObjectRef object = JSValueToObject(ctx, val, NULL)) {
// Check if value is an ArrayBufferView by calling ArrayBuffer.isView(val).
JSObjectRef isViewMethod = RJSValidatedObjectProperty(ctx, arrayBufferConstructor, isViewString);
JSValueRef isView = JSObjectCallAsFunction(ctx, isViewMethod, arrayBufferConstructor, 1, &val, NULL);
if (isView && JSValueToBoolean(ctx, isView) && !RJSIsValueObjectOfType(ctx, val, dataViewString)) {
break;
if (isView && JSValueToBoolean(ctx, isView)) {
uint8ArrayArguments[0] = RJSValidatedObjectProperty(ctx, object, bufferString);
uint8ArrayArguments[1] = RJSValidatedPropertyValue(ctx, object, byteOffsetString);
uint8ArrayArguments[2] = RJSValidatedPropertyValue(ctx, object, byteLengthString);
uint8ArrayArgumentsCount = 3;
}
// Fall through
}
default:
if (!uint8ArrayArgumentsCount) {
throw std::runtime_error("Can only convert ArrayBuffer and TypedArray objects to binary");
}
JSValueRef exception = NULL;
JSObjectRef uint8ArrayContructor = RJSValidatedObjectProperty(ctx, JSContextGetGlobalObject(ctx), uint8ArrayString);
JSObjectRef uint8Array = JSObjectCallAsConstructor(ctx, uint8ArrayContructor, 1, &val, &exception);
JSObjectRef uint8Array = JSObjectCallAsConstructor(ctx, uint8ArrayContructor, uint8ArrayArgumentsCount, uint8ArrayArguments, &exception);
if (exception) {
throw RJSException(ctx, exception);
}

View File

@ -9,10 +9,9 @@ var BaseTest = require('./base-test');
var TestCase = require('./asserts');
var schemas = require('./schemas');
// 31 random bytes (purposefully an odd number).
var RANDOM_DATA = new Uint8Array([
0xd8, 0x21, 0xd6, 0xe8, 0x0c, 0x57, 0xbc, 0xb2, 0x6a, 0x15, 0x77, 0x30, 0xac, 0x77, 0x96, 0xd9,
0x67, 0x1e, 0x40, 0xa7, 0x6d, 0x52, 0x83, 0xda, 0x07, 0x29, 0x9c, 0x70, 0x38, 0x48, 0x4e,
0xd8, 0x21, 0xd6, 0xe8, 0x00, 0x57, 0xbc, 0xb2, 0x6a, 0x15, 0x77, 0x30, 0xac, 0x77, 0x96, 0xd9,
0x67, 0x1e, 0x40, 0xa7, 0x6d, 0x52, 0x83, 0xda, 0x07, 0x29, 0x9c, 0x70, 0x38, 0x48, 0x4e, 0xff,
]);
module.exports = BaseTest.extend({
@ -273,6 +272,49 @@ module.exports = BaseTest.extend({
});
TestCase.assertArraysEqual(new Uint8Array(object.dataCol), RANDOM_DATA);
// Should be to set a data property to a DataView.
realm.write(function() {
object.dataCol = new DataView(RANDOM_DATA.buffer);
});
TestCase.assertArraysEqual(new Uint8Array(object.dataCol), RANDOM_DATA);
// Test that a variety of size and slices of data still work.
[
[0, -1],
[0, -2],
[1, 0],
[1, -1],
[1, -2],
[2, 0],
[2, -1],
[2, -2],
].forEach(function(range) {
var array = RANDOM_DATA.subarray(range[0], range[1]);
realm.write(function() {
// Use a partial "view" of the underlying ArrayBuffer.
object.dataCol = new Uint8Array(RANDOM_DATA.buffer, range[0], array.length);
});
TestCase.assertArraysEqual(new Uint8Array(object.dataCol), array, range.join('...'));
});
// Test other TypedArrays to make sure they all work for setting data properties.
[
Int8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array,
].forEach(function(TypedArray) {
var array = new TypedArray(RANDOM_DATA.buffer);
realm.write(function() {
object.dataCol = array;
});
TestCase.assertArraysEqual(new TypedArray(object.dataCol), array, TypedArray.name);
});
realm.write(function() {
TestCase.assertThrows(function() {
object.dataCol = true;
@ -286,9 +328,6 @@ module.exports = BaseTest.extend({
TestCase.assertThrows(function() {
object.dataCol = [1];
});
TestCase.assertThrows(function() {
object.dataCol = new DataView(RANDOM_DATA.buffer);
});
});
}
});