diff --git a/Jint.Tests/Runtime/Domain/TextDecoder.cs b/Jint.Tests/Runtime/Domain/TextDecoder.cs new file mode 100644 index 0000000000..2e418cc01c --- /dev/null +++ b/Jint.Tests/Runtime/Domain/TextDecoder.cs @@ -0,0 +1,15 @@ +using System.Text; + +namespace Jint.Tests.Runtime.Domain; + +/// +/// https://encoding.spec.whatwg.org/#textdecoder +/// +/// Public API, do not make internal! +public sealed class TextDecoder +{ + public string Decode() => string.Empty; + + + public string Decode(byte[] buff) => Encoding.UTF8.GetString(buff); +} diff --git a/Jint.Tests/Runtime/JsValueConversionTests.cs b/Jint.Tests/Runtime/JsValueConversionTests.cs index 3668e9b15b..b6c991b90d 100644 --- a/Jint.Tests/Runtime/JsValueConversionTests.cs +++ b/Jint.Tests/Runtime/JsValueConversionTests.cs @@ -1,4 +1,5 @@ using Jint.Native; +using Jint.Runtime; namespace Jint.Tests.Runtime { @@ -164,5 +165,38 @@ public void ShouldBeUndefined() Assert.Equal(false, value.IsString()); Assert.Equal(true, value.IsUndefined()); } + + [Fact] + public void ShouldConvertArrayBuffer() + { + var value = _engine.Evaluate("new Uint8Array([102, 111, 111]).buffer"); + Assert.Equal(true, value.IsArrayBuffer()); + Assert.Equal([102, 111, 111], value.AsArrayBuffer()); + Assert.Equal([102, 111, 111], value.ToObject() as byte[]); + + (value as JsArrayBuffer).DetachArrayBuffer(); + + Assert.Equal(true, value.IsArrayBuffer()); + Assert.Equal(null, value.AsArrayBuffer()); + Assert.Throws(value.ToObject); + Assert.Throws(JsValue.Undefined.AsArrayBuffer); + } + + [Fact] + public void ShouldConvertDataView() + { + var value = _engine.Evaluate("new DataView(new Uint8Array([102, 102, 111, 111, 111]).buffer, 1, 3)"); + + Assert.Equal(true, value.IsDataView()); + Assert.Equal([102, 111, 111], value.AsDataView()); + Assert.Equal([102, 111, 111], value.ToObject() as byte[]); + + (value as JsDataView)._viewedArrayBuffer.DetachArrayBuffer(); + + Assert.Equal(true, value.IsDataView()); + Assert.Equal(null, value.AsDataView()); + Assert.Throws(value.ToObject); + Assert.Throws(JsValue.Undefined.AsDataView); + } } } diff --git a/Jint.Tests/Runtime/TextTests.cs b/Jint.Tests/Runtime/TextTests.cs new file mode 100644 index 0000000000..8be9d53199 --- /dev/null +++ b/Jint.Tests/Runtime/TextTests.cs @@ -0,0 +1,33 @@ +using Jint.Runtime.Interop; +using Jint.Tests.Runtime.Domain; + +namespace Jint.Tests.Runtime; + +public sealed class TextTests +{ + private readonly Engine _engine; + + public TextTests() + { + _engine = new Engine() + .SetValue("log", new Action(Console.WriteLine)) + .SetValue("assert", new Action(Assert.True)) + .SetValue("equal", new Action(Assert.Equal)); + _engine + .SetValue("TextDecoder", TypeReference.CreateTypeReference(_engine)) + ; + } + private object RunTest(string source) + { + return _engine.Evaluate(source).ToObject(); + } + + [Fact] + public void CanDecode() + { + Assert.Equal("", RunTest($"new TextDecoder().decode()")); + Assert.Equal("foo", RunTest($"new TextDecoder().decode(new Uint8Array([102,111,111]))")); + Assert.Equal("foo", RunTest($"new TextDecoder().decode(new Uint8Array([102,111,111]).buffer)")); + Assert.Equal("foo", RunTest($"new TextDecoder().decode(new DataView(new Uint8Array([0,102,111,111,0]).buffer,1,3))")); + } +} diff --git a/Jint/JsValueExtensions.cs b/Jint/JsValueExtensions.cs index e974b47be3..e23834a577 100644 --- a/Jint/JsValueExtensions.cs +++ b/Jint/JsValueExtensions.cs @@ -246,6 +246,52 @@ public static string AsString(this JsValue value) return value.ToString(); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsArrayBuffer(this JsValue value) + { + return value is JsArrayBuffer; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte[]? AsArrayBuffer(this JsValue value) + { + if (!value.IsArrayBuffer()) + { + ThrowWrongTypeException(value, "ArrayBuffer"); + } + + return ((JsArrayBuffer) value)._arrayBufferData; + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsDataView(this JsValue value) + { + return value is JsDataView; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte[]? AsDataView(this JsValue value) + { + if (!value.IsDataView()) + { + ThrowWrongTypeException(value, "DataView"); + } + + var dataView = (JsDataView) value; + + if (dataView._viewedArrayBuffer?._arrayBufferData == null) + { + return null; // should not happen + } + + // create view + var res = new byte[dataView._byteLength]; + Array.Copy(dataView._viewedArrayBuffer._arrayBufferData!, dataView._byteOffset, res, 0, dataView._byteLength); + return res; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsUint8Array(this JsValue value) { diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index 3d34fe0f4c..c54f4ca9bc 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -1081,6 +1081,24 @@ private object ToObject(ObjectTraverseStack stack) break; } + if (this is JsArrayBuffer arrayBuffer) + { + // TODO: What to do here when buffer is detached? We're not allowed to return null + arrayBuffer.AssertNotDetached(); + converted = arrayBuffer.ArrayBufferData; + break; + } + + if (this is JsDataView dataView) + { + // TODO: What to do here when buffer is detached? We're not allowed to return null + dataView._viewedArrayBuffer!.AssertNotDetached(); + var res = new byte[dataView._byteLength]; + System.Array.Copy(dataView._viewedArrayBuffer._arrayBufferData!, dataView._byteOffset, res, 0, dataView._byteLength); + converted = res; + break; + } + if (this is BigIntInstance bigIntInstance) { converted = bigIntInstance.BigIntData._value;