using System; using System.Collections.Generic; using System.Linq; using Jint.Collections; using Jint.Native.Array; using Jint.Native.ArrayBuffer; using Jint.Native.Iterator; using Jint.Native.Number; using Jint.Native.Object; using Jint.Native.Symbol; using Jint.Pooling; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Interop; namespace Jint.Native.TypedArray { /// /// https://tc39.es/ecma262/#sec-properties-of-the-%typedarrayprototype%-object /// internal sealed class IntrinsicTypedArrayPrototype : ObjectInstance { private readonly Realm _realm; private readonly IntrinsicTypedArrayConstructor _constructor; private ClrFunctionInstance _originalIteratorFunction; internal IntrinsicTypedArrayPrototype( Engine engine, Realm realm, ObjectInstance objectPrototype, IntrinsicTypedArrayConstructor constructor) : base(engine) { _prototype = objectPrototype; _realm = realm; _constructor = constructor; } protected override void Initialize() { const PropertyFlag lengthFlags = PropertyFlag.Configurable; const PropertyFlag propertyFlags = PropertyFlag.Writable | PropertyFlag.Configurable; var properties = new PropertyDictionary(31, false) { ["buffer"] = new GetSetPropertyDescriptor(new ClrFunctionInstance(_engine, "get buffer", Buffer, 0, lengthFlags), Undefined, PropertyFlag.Configurable), ["byteLength"] = new GetSetPropertyDescriptor(new ClrFunctionInstance(_engine, "get byteLength", ByteLength, 0, lengthFlags), Undefined, PropertyFlag.Configurable), ["byteOffset"] = new GetSetPropertyDescriptor(new ClrFunctionInstance(Engine, "get byteOffset", ByteOffset, 0, lengthFlags), Undefined, PropertyFlag.Configurable), ["length"] = new GetSetPropertyDescriptor(new ClrFunctionInstance(Engine, "get length", GetLength, 0, lengthFlags), Undefined, PropertyFlag.Configurable), ["constructor"] = new(_constructor, PropertyFlag.NonEnumerable), ["copyWithin"] = new(new ClrFunctionInstance(Engine, "copyWithin", CopyWithin, 2, PropertyFlag.Configurable), propertyFlags), ["entries"] = new(new ClrFunctionInstance(Engine, "entries", Entries, 0, PropertyFlag.Configurable), propertyFlags), ["every"] = new(new ClrFunctionInstance(Engine, "every", Every, 1, PropertyFlag.Configurable), propertyFlags), ["fill"] = new(new ClrFunctionInstance(Engine, "fill", Fill, 1, PropertyFlag.Configurable), propertyFlags), ["filter"] = new(new ClrFunctionInstance(Engine, "filter", Filter, 1, PropertyFlag.Configurable), propertyFlags), ["find"] = new(new ClrFunctionInstance(Engine, "find", Find, 1, PropertyFlag.Configurable), propertyFlags), ["findIndex"] = new(new ClrFunctionInstance(Engine, "findIndex", FindIndex, 1, PropertyFlag.Configurable), propertyFlags), ["findLast"] = new(new ClrFunctionInstance(Engine, "findLast", FindLast, 1, PropertyFlag.Configurable), propertyFlags), ["findLastIndex"] = new(new ClrFunctionInstance(Engine, "findLastIndex", FindLastIndex, 1, PropertyFlag.Configurable), propertyFlags), ["forEach"] = new(new ClrFunctionInstance(Engine, "forEach", ForEach, 1, PropertyFlag.Configurable), propertyFlags), ["includes"] = new(new ClrFunctionInstance(Engine, "includes", Includes, 1, PropertyFlag.Configurable), propertyFlags), ["indexOf"] = new(new ClrFunctionInstance(Engine, "indexOf", IndexOf, 1, PropertyFlag.Configurable), propertyFlags), ["join"] = new(new ClrFunctionInstance(Engine, "join", Join, 1, PropertyFlag.Configurable), propertyFlags), ["keys"] = new(new ClrFunctionInstance(Engine, "keys", Keys, 0, PropertyFlag.Configurable), propertyFlags), ["lastIndexOf"] = new(new ClrFunctionInstance(Engine, "lastIndexOf", LastIndexOf, 1, PropertyFlag.Configurable), propertyFlags), ["map"] = new(new ClrFunctionInstance(Engine, "map", Map, 1, PropertyFlag.Configurable), propertyFlags), ["reduce"] = new(new ClrFunctionInstance(Engine, "reduce", Reduce, 1, PropertyFlag.Configurable), propertyFlags), ["reduceRight"] = new(new ClrFunctionInstance(Engine, "reduceRight", ReduceRight, 1, PropertyFlag.Configurable), propertyFlags), ["reverse"] = new(new ClrFunctionInstance(Engine, "reverse", Reverse, 0, PropertyFlag.Configurable), propertyFlags), ["set"] = new(new ClrFunctionInstance(Engine, "set", Set, 1, PropertyFlag.Configurable), propertyFlags), ["slice"] = new(new ClrFunctionInstance(Engine, "slice", Slice, 2, PropertyFlag.Configurable), propertyFlags), ["some"] = new(new ClrFunctionInstance(Engine, "some", Some, 1, PropertyFlag.Configurable), propertyFlags), ["sort"] = new(new ClrFunctionInstance(Engine, "sort", Sort, 1, PropertyFlag.Configurable), propertyFlags), ["subarray"] = new(new ClrFunctionInstance(Engine, "subarray", Subarray, 2, PropertyFlag.Configurable), propertyFlags), ["toLocaleString"] = new(new ClrFunctionInstance(Engine, "toLocaleString", ToLocaleString, 0, PropertyFlag.Configurable), propertyFlags), ["toString"] = new(new ClrFunctionInstance(Engine, "toLocaleString", _realm.Intrinsics.Array.PrototypeObject.ToString, 0, PropertyFlag.Configurable), propertyFlags), ["values"] = new(new ClrFunctionInstance(Engine, "values", Values, 0, PropertyFlag.Configurable), propertyFlags), ["at"] = new(new ClrFunctionInstance(Engine, "at", At, 1, PropertyFlag.Configurable), propertyFlags), }; SetProperties(properties); _originalIteratorFunction = new ClrFunctionInstance(Engine, "iterator", Values, 1); var symbols = new SymbolDictionary(2) { [GlobalSymbolRegistry.Iterator] = new(_originalIteratorFunction, propertyFlags), [GlobalSymbolRegistry.ToStringTag] = new GetSetPropertyDescriptor( new ClrFunctionInstance(Engine, "get [Symbol.toStringTag]", ToStringTag, 0, PropertyFlag.Configurable), Undefined, PropertyFlag.Configurable) }; SetSymbols(symbols); } /// /// https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.buffer /// private JsValue Buffer(JsValue thisObj, JsValue[] arguments) { var o = thisObj as TypedArrayInstance; if (o is null) { ExceptionHelper.ThrowTypeError(_realm); } return o._viewedArrayBuffer; } /// /// https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.bytelength /// private JsValue ByteLength(JsValue thisObj, JsValue[] arguments) { var o = thisObj as TypedArrayInstance; if (o is null) { ExceptionHelper.ThrowTypeError(_realm); } if (o._viewedArrayBuffer.IsDetachedBuffer) { return JsNumber.PositiveZero; } return JsNumber.Create(o._byteLength); } /// /// https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.byteoffset /// private JsValue ByteOffset(JsValue thisObj, JsValue[] arguments) { var o = thisObj as TypedArrayInstance; if (o is null) { ExceptionHelper.ThrowTypeError(_realm); } if (o._viewedArrayBuffer.IsDetachedBuffer) { return JsNumber.PositiveZero; } return JsNumber.Create(o._byteOffset); } /// /// https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.length /// private JsValue GetLength(JsValue thisObj, JsValue[] arguments) { var o = thisObj as TypedArrayInstance; if (o is null) { ExceptionHelper.ThrowTypeError(_realm); } var buffer = o._viewedArrayBuffer; if (buffer.IsDetachedBuffer) { return JsNumber.PositiveZero; } return JsNumber.Create(o.Length); } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.copywithin /// private JsValue CopyWithin(JsValue thisObj, JsValue[] arguments) { var o = thisObj.ValidateTypedArray(_realm); var target = arguments.At(0); var start = arguments.At(1); var end = arguments.At(2); long len = o.Length; var relativeTarget = TypeConverter.ToIntegerOrInfinity(target); long to; if (double.IsNegativeInfinity(relativeTarget)) { to = 0; } else if (relativeTarget < 0) { to = (long) System.Math.Max(len + relativeTarget, 0); } else { to = (long) System.Math.Min(relativeTarget, len); } var relativeStart = TypeConverter.ToIntegerOrInfinity(start); long from; if (double.IsNegativeInfinity(relativeStart)) { from = 0; } else if (relativeStart < 0) { from = (long) System.Math.Max(len + relativeStart, 0); } else { from = (long) System.Math.Min(relativeStart, len); } var relativeEnd = end.IsUndefined() ? len : TypeConverter.ToIntegerOrInfinity(end); long final; if (double.IsNegativeInfinity(relativeEnd)) { final = 0; } else if (relativeEnd < 0) { final = (long) System.Math.Max(len + relativeEnd, 0); } else { final = (long) System.Math.Min(relativeEnd, len); } var count = System.Math.Min(final - from, len - to); if (count > 0) { var buffer = o._viewedArrayBuffer; buffer.AssertNotDetached(); var elementSize = o._arrayElementType.GetElementSize(); var byteOffset = o._byteOffset; var toByteIndex = to * elementSize + byteOffset; var fromByteIndex = from * elementSize + byteOffset; var countBytes = count * elementSize; int direction; if (fromByteIndex < toByteIndex && toByteIndex < fromByteIndex + countBytes) { direction = -1; fromByteIndex = fromByteIndex + countBytes - 1; toByteIndex = toByteIndex + countBytes - 1; } else { direction = 1; } while (countBytes > 0) { var value = buffer.GetValueFromBuffer((int) fromByteIndex, TypedArrayElementType.Uint8, true, ArrayBufferOrder.Unordered); buffer.SetValueInBuffer((int) toByteIndex, TypedArrayElementType.Uint8, value, true, ArrayBufferOrder.Unordered); fromByteIndex += direction; toByteIndex += direction; countBytes--; } } return o; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.entries /// private JsValue Entries(JsValue thisObj, JsValue[] arguments) { var o = thisObj.ValidateTypedArray(_realm); return _realm.Intrinsics.ArrayIteratorPrototype.Construct(o, ArrayIteratorType.KeyAndValue); } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.every /// private JsValue Every(JsValue thisObj, JsValue[] arguments) { var o = thisObj.ValidateTypedArray(_realm); var len = o.Length; if (len == 0) { return JsBoolean.True; } var predicate = GetCallable(arguments.At(0)); var thisArg = arguments.At(1); var args = _engine._jsValueArrayPool.RentArray(3); args[2] = o; for (var k = 0; k < len; k++) { args[0] = o[k]; args[1] = k; if (!TypeConverter.ToBoolean(predicate.Call(thisArg, args))) { return JsBoolean.False; } } _engine._jsValueArrayPool.ReturnArray(args); return JsBoolean.True; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.fill /// private JsValue Fill(JsValue thisObj, JsValue[] arguments) { var o = thisObj.ValidateTypedArray(_realm); var jsValue = arguments.At(0); var start = arguments.At(1); var end = arguments.At(2); JsValue value; if (o._contentType == TypedArrayContentType.BigInt) { value = JsBigInt.Create(jsValue.ToBigInteger(_engine)); } else { value = JsNumber.Create(jsValue); } var len = o.Length; int k; var relativeStart = TypeConverter.ToIntegerOrInfinity(start); if (double.IsNegativeInfinity(relativeStart)) { k = 0; } else if (relativeStart < 0) { k = (int) System.Math.Max(len + relativeStart, 0); } else { k = (int) System.Math.Min(relativeStart, len); } uint final; var relativeEnd = end.IsUndefined() ? len : TypeConverter.ToIntegerOrInfinity(end); if (double.IsNegativeInfinity(relativeEnd)) { final = 0; } else if (relativeEnd < 0) { final = (uint) System.Math.Max(len + relativeEnd, 0); } else { final = (uint) System.Math.Min(relativeEnd, len); } o._viewedArrayBuffer.AssertNotDetached(); for (var i = k; i < final; ++i) { o[i] = value; } return thisObj; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.filter /// private JsValue Filter(JsValue thisObj, JsValue[] arguments) { var callbackfn = GetCallable(arguments.At(0)); var thisArg = arguments.At(1); var o = thisObj.ValidateTypedArray(_realm); var len = o.Length; var kept = new List(); var captured = 0; var args = _engine._jsValueArrayPool.RentArray(3); args[2] = o; for (var k = 0; k < len; k++) { var kValue = o[k]; args[0] = kValue; args[1] = k; var selected = callbackfn.Call(thisArg, args); if (TypeConverter.ToBoolean(selected)) { kept.Add(kValue); captured++; } } _engine._jsValueArrayPool.ReturnArray(args); var a = _realm.Intrinsics.TypedArray.TypedArraySpeciesCreate(o, new JsValue[] { captured }); for (var n = 0; n < captured; ++n) { a[n] = kept[n]; } return a; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.find /// private JsValue Find(JsValue thisObj, JsValue[] arguments) { return DoFind(thisObj, arguments).Value; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.findindex /// private JsValue FindIndex(JsValue thisObj, JsValue[] arguments) { return DoFind(thisObj, arguments).Key; } private JsValue FindLast(JsValue thisObj, JsValue[] arguments) { return DoFind(thisObj, arguments, fromEnd: true).Value; } private JsValue FindLastIndex(JsValue thisObj, JsValue[] arguments) { return DoFind(thisObj, arguments, fromEnd: true).Key; } private KeyValuePair DoFind(JsValue thisObj, JsValue[] arguments, bool fromEnd = false) { var o = thisObj.ValidateTypedArray(_realm); var len = (int) o.Length; var predicate = GetCallable(arguments.At(0)); var thisArg = arguments.At(1); var args = _engine._jsValueArrayPool.RentArray(3); args[2] = o; if (!fromEnd) { for (var k = 0; k < len; k++) { var kNumber = JsNumber.Create(k); var kValue = o[k]; args[0] = kValue; args[1] = kNumber; if (TypeConverter.ToBoolean(predicate.Call(thisArg, args))) { return new KeyValuePair(kNumber, kValue); } } } else { for (var k = len - 1; k >= 0; k--) { var kNumber = JsNumber.Create(k); var kValue = o[k]; args[0] = kValue; args[1] = kNumber; if (TypeConverter.ToBoolean(predicate.Call(thisArg, args))) { return new KeyValuePair(kNumber, kValue); } } } return new KeyValuePair(JsNumber.IntegerNegativeOne, Undefined); } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.foreach /// private JsValue ForEach(JsValue thisObj, JsValue[] arguments) { var callbackfn = GetCallable(arguments.At(0)); var thisArg = arguments.At(1); var o = thisObj.ValidateTypedArray(_realm); var len = o.Length; var args = _engine._jsValueArrayPool.RentArray(3); args[2] = o; for (var k = 0; k < len; k++) { var kValue = o[k]; args[0] = kValue; args[1] = k; callbackfn.Call(thisArg, args); } _engine._jsValueArrayPool.ReturnArray(args); return Undefined; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.includes /// private JsValue Includes(JsValue thisObj, JsValue[] arguments) { var o = thisObj.ValidateTypedArray(_realm); var len = o.Length; if (len == 0) { return false; } var searchElement = arguments.At(0); var fromIndex = arguments.At(1, 0); var n = TypeConverter.ToIntegerOrInfinity(fromIndex); if (double.IsPositiveInfinity(n)) { return JsBoolean.False; } else if (double.IsNegativeInfinity(n)) { n = 0; } long k; if (n >= 0) { k = (long) n; } else { k = (long) (len + n); if (k < 0) { k = 0; } } while (k < len) { var value = o[(int) k]; if (SameValueZeroComparer.Equals(value, searchElement)) { return JsBoolean.True; } k++; } return JsBoolean.False; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.indexof /// private JsValue IndexOf(JsValue thisObj, JsValue[] arguments) { var searchElement = arguments.At(0); var fromIndex = arguments.At(1); var o = thisObj.ValidateTypedArray(_realm); var len = o.Length; if (len == 0) { return JsNumber.IntegerNegativeOne; } var n = TypeConverter.ToIntegerOrInfinity(fromIndex); if (double.IsPositiveInfinity(n)) { return JsNumber.IntegerNegativeOne; } else if (double.IsNegativeInfinity(n)) { n = 0; } long k; if (n >= 0) { k = (long) n; } else { k = (long) (len + n); if (k < 0) { k = 0; } } for (; k < len; k++) { var kPresent = o.HasProperty(k); if (kPresent) { var elementK = o[(int) k]; if (elementK == searchElement) { return k; } } } return JsNumber.IntegerNegativeOne; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.join /// private JsValue Join(JsValue thisObj, JsValue[] arguments) { var o = thisObj.ValidateTypedArray(_realm); var separator = arguments.At(0); var len = o.Length; var sep = TypeConverter.ToString(separator.IsUndefined() ? JsString.CommaString : separator); // as per the spec, this has to be called after ToString(separator) if (len == 0) { return JsString.Empty; } static string StringFromJsValue(JsValue value) { return value.IsUndefined() ? "" : TypeConverter.ToString(value); } var s = StringFromJsValue(o[0]); if (len == 1) { return s; } using var sb = StringBuilderPool.Rent(); sb.Builder.Append(s); for (var k = 1; k < len; k++) { sb.Builder.Append(sep); sb.Builder.Append(StringFromJsValue(o[k])); } return sb.ToString(); } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.keys /// private JsValue Keys(JsValue thisObj, JsValue[] arguments) { var o = thisObj.ValidateTypedArray(_realm); return _realm.Intrinsics.ArrayIteratorPrototype.Construct(o, ArrayIteratorType.Key); } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.lastindexof /// private JsValue LastIndexOf(JsValue thisObj, JsValue[] arguments) { var searchElement = arguments.At(0); var o = thisObj.ValidateTypedArray(_realm); var len = o.Length; if (len == 0) { return JsNumber.IntegerNegativeOne; } var fromIndex = arguments.At(1, len - 1); var n = TypeConverter.ToIntegerOrInfinity(fromIndex); if (double.IsNegativeInfinity(n)) { return JsNumber.IntegerNegativeOne; } long k; if (n >= 0) { k = (long) System.Math.Min(n, len - 1); } else { k = (long) (len + n); } for (; k >= 0; k--) { var kPresent = o.HasProperty(k); if (kPresent) { var elementK = o[(int) k]; if (elementK == searchElement) { return k; } } } return JsNumber.IntegerNegativeOne; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.map /// private ObjectInstance Map(JsValue thisObj, JsValue[] arguments) { var o = thisObj.ValidateTypedArray(_realm); var len = o.Length; var thisArg = arguments.At(1); var callable = GetCallable(arguments.At(0)); var a = _realm.Intrinsics.TypedArray.TypedArraySpeciesCreate(o, new JsValue[] { len }); var args = _engine._jsValueArrayPool.RentArray(3); args[2] = o; for (var k = 0; k < len; k++) { args[0] = o[k]; args[1] = k; var mappedValue = callable.Call(thisArg, args); a[k] = mappedValue; } _engine._jsValueArrayPool.ReturnArray(args); return a; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduce /// private JsValue Reduce(JsValue thisObj, JsValue[] arguments) { var callbackfn = GetCallable(arguments.At(0)); var initialValue = arguments.At(1); var o = thisObj.ValidateTypedArray(_realm); var len = o.Length; if (len == 0 && arguments.Length < 2) { ExceptionHelper.ThrowTypeError(_realm); } var k = 0; var accumulator = Undefined; if (!initialValue.IsUndefined()) { accumulator = initialValue; } else { accumulator = o[k]; k++; } var args = _engine._jsValueArrayPool.RentArray(4); args[3] = o; while (k < len) { var kValue = o[k]; args[0] = accumulator; args[1] = kValue; args[2] = k; accumulator = callbackfn.Call(Undefined, args); k++; } _engine._jsValueArrayPool.ReturnArray(args); return accumulator; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduceright /// private JsValue ReduceRight(JsValue thisObj, JsValue[] arguments) { var callbackfn = GetCallable(arguments.At(0)); var initialValue = arguments.At(1); var o = thisObj.ValidateTypedArray(_realm); var len = (int) o.Length; if (len == 0 && arguments.Length < 2) { ExceptionHelper.ThrowTypeError(_realm); } var k = len - 1; JsValue accumulator; if (arguments.Length > 1) { accumulator = initialValue; } else { accumulator = o[k]; k--; } var jsValues = _engine._jsValueArrayPool.RentArray(4); jsValues[3] = o; for (; k >= 0; k--) { jsValues[0] = accumulator; jsValues[1] = o[k]; jsValues[2] = k; accumulator = callbackfn.Call(Undefined, jsValues); } _engine._jsValueArrayPool.ReturnArray(jsValues); return accumulator; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.reverse /// private ObjectInstance Reverse(JsValue thisObj, JsValue[] arguments) { var o = thisObj.ValidateTypedArray(_realm); var len = (int) o.Length; var middle = (int) System.Math.Floor(len / 2.0); var lower = 0; while (lower != middle) { var upper = len - lower - 1; var lowerValue = o[lower]; var upperValue = o[upper]; o[lower] = upperValue; o[upper] = lowerValue; lower++; } return o; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.set /// private JsValue Set(JsValue thisObj, JsValue[] arguments) { var target = thisObj as TypedArrayInstance; if (target is null) { ExceptionHelper.ThrowTypeError(_realm); } var source = arguments.At(0); var offset = arguments.At(1); var targetOffset = TypeConverter.ToIntegerOrInfinity(offset); if (targetOffset < 0) { ExceptionHelper.ThrowRangeError(_realm, "Invalid offset"); } if (source is TypedArrayInstance typedArrayInstance) { SetTypedArrayFromTypedArray(target, targetOffset, typedArrayInstance); } else { SetTypedArrayFromArrayLike(target, targetOffset, source); } return Undefined; } /// /// https://tc39.es/ecma262/#sec-settypedarrayfromtypedarray /// private void SetTypedArrayFromTypedArray(TypedArrayInstance target, double targetOffset, TypedArrayInstance source) { var targetBuffer = target._viewedArrayBuffer; targetBuffer.AssertNotDetached(); var targetLength = target._arrayLength; var srcBuffer = source._viewedArrayBuffer; srcBuffer.AssertNotDetached(); var targetType = target._arrayElementType; var targetElementSize = targetType.GetElementSize(); var targetByteOffset = target._byteOffset; var srcType = source._arrayElementType; var srcElementSize = srcType.GetElementSize(); var srcLength = source._arrayLength; var srcByteOffset = source._byteOffset; if (double.IsNegativeInfinity(targetOffset)) { ExceptionHelper.ThrowRangeError(_realm, "Invalid target offset"); } if (srcLength + targetOffset > targetLength) { ExceptionHelper.ThrowRangeError(_realm, "Invalid target offset"); } if (target._contentType != source._contentType) { ExceptionHelper.ThrowTypeError(_realm, "Content type mismatch"); } bool same; if (srcBuffer.IsSharedArrayBuffer && targetBuffer.IsSharedArrayBuffer) { // a. If srcBuffer.[[ArrayBufferData]] and targetBuffer.[[ArrayBufferData]] are the same Shared Data Block values, let same be true; else let same be false. ExceptionHelper.ThrowNotImplementedException("SharedBuffer not implemented"); same = false; } else { same = SameValue(srcBuffer, targetBuffer); } int srcByteIndex; if (same) { var srcByteLength = source._byteLength; srcBuffer = srcBuffer.CloneArrayBuffer(_realm.Intrinsics.ArrayBuffer, srcByteOffset, srcByteLength, _realm.Intrinsics.ArrayBuffer); // %ArrayBuffer% is used to clone srcBuffer because is it known to not have any observable side-effects. srcByteIndex = 0; } else { srcByteIndex = srcByteOffset; } var targetByteIndex = (int) (targetOffset * targetElementSize + targetByteOffset); var limit = targetByteIndex + targetElementSize * srcLength; if (srcType == targetType) { // NOTE: If srcType and targetType are the same, the transfer must be performed in a manner that preserves the bit-level encoding of the source data. while (targetByteIndex < limit) { var value = srcBuffer.GetValueFromBuffer(srcByteIndex, TypedArrayElementType.Uint8, true, ArrayBufferOrder.Unordered); targetBuffer.SetValueInBuffer(targetByteIndex, TypedArrayElementType.Uint8, value, true, ArrayBufferOrder.Unordered); srcByteIndex += 1; targetByteIndex += 1; } } else { while (targetByteIndex < limit) { var value = srcBuffer.GetValueFromBuffer(srcByteIndex, srcType, true, ArrayBufferOrder.Unordered); targetBuffer.SetValueInBuffer(targetByteIndex, targetType, value, true, ArrayBufferOrder.Unordered); srcByteIndex += srcElementSize; targetByteIndex += targetElementSize; } } } /// /// https://tc39.es/ecma262/#sec-settypedarrayfromarraylike /// private void SetTypedArrayFromArrayLike(TypedArrayInstance target, double targetOffset, JsValue source) { var targetBuffer = target._viewedArrayBuffer; targetBuffer.AssertNotDetached(); var targetLength = target._arrayLength; var targetElementSize = target._arrayElementType.GetElementSize(); var targetType = target._arrayElementType; var targetByteOffset = target._byteOffset; var src = ArrayOperations.For(TypeConverter.ToObject(_realm, source)); var srcLength = src.GetLength(); if (double.IsNegativeInfinity(targetOffset)) { ExceptionHelper.ThrowRangeError(_realm, "Invalid target offset"); } if (srcLength + targetOffset > targetLength) { ExceptionHelper.ThrowRangeError(_realm, "Invalid target offset"); } var targetByteIndex = targetOffset * targetElementSize + targetByteOffset; ulong k = 0; var limit = targetByteIndex + targetElementSize * srcLength; while (targetByteIndex < limit) { if (target._contentType == TypedArrayContentType.BigInt) { var value = src.Get(k).ToBigInteger(_engine); targetBuffer.AssertNotDetached(); targetBuffer.SetValueInBuffer((int) targetByteIndex, targetType, value, true, ArrayBufferOrder.Unordered); } else { var value = TypeConverter.ToNumber(src.Get(k)); targetBuffer.AssertNotDetached(); targetBuffer.SetValueInBuffer((int) targetByteIndex, targetType, value, true, ArrayBufferOrder.Unordered); } k++; targetByteIndex += targetElementSize; } } /// /// https://tc39.es/proposal-relative-indexing-method/#sec-%typedarray.prototype%-additions /// private JsValue At(JsValue thisObj, JsValue[] arguments) { var start = arguments.At(0); var o = thisObj.ValidateTypedArray(_realm); long len = o.Length; var relativeStart = TypeConverter.ToInteger(start); int k; if (relativeStart < 0) { k = (int) (len + relativeStart); } else { k = (int) relativeStart; } if(k < 0 || k >= len) { return Undefined; } return o.Get(k); } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.slice /// private JsValue Slice(JsValue thisObj, JsValue[] arguments) { var start = arguments.At(0); var end = arguments.At(1); var o = thisObj.ValidateTypedArray(_realm); long len = o.Length; var relativeStart = TypeConverter.ToIntegerOrInfinity(start); int k; if (double.IsNegativeInfinity(relativeStart)) { k = 0; } else if (relativeStart < 0) { k = (int) System.Math.Max(len + relativeStart, 0); } else { k = (int) System.Math.Min(relativeStart, len); } var relativeEnd = end.IsUndefined() ? len : TypeConverter.ToIntegerOrInfinity(end); long final; if (double.IsNegativeInfinity(relativeEnd)) { final = 0; } else if (relativeEnd < 0) { final = (long) System.Math.Max(len + relativeEnd, 0); } else { final = (long) System.Math.Min(relativeEnd, len); } var count = System.Math.Max(final - k, 0); var a = _realm.Intrinsics.TypedArray.TypedArraySpeciesCreate(o, new JsValue[] { count }); if (count > 0) { o._viewedArrayBuffer.AssertNotDetached(); var srcType = o._arrayElementType; var targetType = a._arrayElementType; if (srcType != targetType) { var n = 0; while (k < final) { var kValue = o[k]; a[n] = kValue; k++; n++; } } else { var srcBuffer = o._viewedArrayBuffer; var targetBuffer = a._viewedArrayBuffer; var elementSize = srcType.GetElementSize(); var srcByteOffset = o._byteOffset; var targetByteIndex = a._byteOffset; var srcByteIndex = (int) k * elementSize + srcByteOffset; var limit = targetByteIndex + count * elementSize; while (targetByteIndex < limit) { var value = srcBuffer.GetValueFromBuffer(srcByteIndex, TypedArrayElementType.Uint8, true, ArrayBufferOrder.Unordered); targetBuffer.SetValueInBuffer(targetByteIndex, TypedArrayElementType.Uint8, value, true, ArrayBufferOrder.Unordered); srcByteIndex++; targetByteIndex++; } } } return a; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.some /// private JsValue Some(JsValue thisObj, JsValue[] arguments) { var o = thisObj.ValidateTypedArray(_realm); var len = o.Length; var callbackfn = GetCallable(arguments.At(0)); var thisArg = arguments.At(1); var args = _engine._jsValueArrayPool.RentArray(3); args[2] = o; for (var k = 0; k < len; k++) { args[0] = o[k]; args[1] = k; if (TypeConverter.ToBoolean(callbackfn.Call(thisArg, args))) { return JsBoolean.True; } } _engine._jsValueArrayPool.ReturnArray(args); return JsBoolean.False; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.sort /// private JsValue Sort(JsValue thisObj, JsValue[] arguments) { /* * %TypedArray%.prototype.sort is a distinct function that, except as described below, * implements the same requirements as those of Array.prototype.sort as defined in 23.1.3.27. * The implementation of the %TypedArray%.prototype.sort specification may be optimized with the knowledge that the this value is * an object that has a fixed length and whose integer-indexed properties are not sparse. */ var obj = thisObj.ValidateTypedArray(_realm); var buffer = obj._viewedArrayBuffer; var len = obj.Length; var compareArg = arguments.At(0); ICallable compareFn = null; if (!compareArg.IsUndefined()) { compareFn = GetCallable(compareArg); } if (len <= 1) { return obj; } JsValue[] array; try { var comparer = TypedArrayComparer.WithFunction(buffer, compareFn); var operations = ArrayOperations.For(obj); array = operations .OrderBy(x => x, comparer) .ToArray(); } catch (InvalidOperationException e) { throw e.InnerException ?? e; } for (var i = 0; i < (uint) array.Length; ++i) { obj[i] = array[i]; } return obj; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.subarray /// private JsValue Subarray(JsValue thisObj, JsValue[] arguments) { var o = thisObj as TypedArrayInstance; if (o is null) { ExceptionHelper.ThrowTypeError(_realm); } var begin = arguments.At(0); var end = arguments.At(1); var buffer = o._viewedArrayBuffer; var srcLength = o.Length; var relativeBegin = TypeConverter.ToIntegerOrInfinity(begin); double beginIndex; if (double.IsNegativeInfinity(relativeBegin)) { beginIndex = 0; } else if (relativeBegin < 0) { beginIndex = System.Math.Max(srcLength + relativeBegin, 0); } else { beginIndex = System.Math.Min(relativeBegin, srcLength); } double relativeEnd; if (end.IsUndefined()) { relativeEnd = srcLength; } else { relativeEnd = TypeConverter.ToIntegerOrInfinity(end); } double endIndex; if (double.IsNegativeInfinity(relativeEnd)) { endIndex = 0; } else if (relativeEnd < 0) { endIndex = System.Math.Max(srcLength + relativeEnd, 0); } else { endIndex = System.Math.Min(relativeEnd, srcLength); } var newLength = System.Math.Max(endIndex - beginIndex, 0); var elementSize = o._arrayElementType.GetElementSize(); var srcByteOffset = o._byteOffset; var beginByteOffset = srcByteOffset + beginIndex * elementSize; var argumentsList = new JsValue[] { buffer, beginByteOffset, newLength }; return _realm.Intrinsics.TypedArray.TypedArraySpeciesCreate(o, argumentsList); } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.tolocalestring /// private JsValue ToLocaleString(JsValue thisObj, JsValue[] arguments) { /* * %TypedArray%.prototype.toLocaleString is a distinct function that implements the same algorithm as Array.prototype.toLocaleString * as defined in 23.1.3.29 except that the this value's [[ArrayLength]] internal slot is accessed in place of performing * a [[Get]] of "length". The implementation of the algorithm may be optimized with the knowledge that the this value is an object * that has a fixed length and whose integer-indexed properties are not sparse. However, such optimization must not introduce * any observable changes in the specified behaviour of the algorithm. */ var array = thisObj.ValidateTypedArray(_realm); var len = array.Length; const string separator = ","; if (len == 0) { return JsString.Empty; } JsValue r; if (!array.TryGetValue(0, out var firstElement) || firstElement.IsNull() || firstElement.IsUndefined()) { r = JsString.Empty; } else { var elementObj = TypeConverter.ToObject(_realm, firstElement); var func = elementObj.Get("toLocaleString", elementObj) as ICallable; if (func is null) { ExceptionHelper.ThrowTypeError(_realm); } r = func.Call(elementObj, Arguments.Empty); } for (var k = 1; k < len; k++) { var s = r + separator; var elementObj = TypeConverter.ToObject(_realm, array[k]); var func = elementObj.Get("toLocaleString", elementObj) as ICallable; if (func is null) { ExceptionHelper.ThrowTypeError(_realm); } r = func.Call(elementObj, Arguments.Empty); r = s + r; } return r; } /// /// https://tc39.es/ecma262/#sec-%typedarray%.prototype.values /// private JsValue Values(JsValue thisObj, JsValue[] arguments) { var o = thisObj.ValidateTypedArray(_realm); return _realm.Intrinsics.ArrayIteratorPrototype.Construct(o, ArrayIteratorType.Value); } /// /// https://tc39.es/ecma262/#sec-get-%typedarray%.prototype-@@tostringtag /// private static JsValue ToStringTag(JsValue thisObj, JsValue[] arguments) { if (thisObj is not TypedArrayInstance o) { return Undefined; } return o._arrayElementType.GetTypedArrayName(); } private sealed class TypedArrayComparer : IComparer { public static TypedArrayComparer WithFunction(ArrayBufferInstance buffer, ICallable compare) { return new TypedArrayComparer(buffer, compare); } private readonly ArrayBufferInstance _buffer; private readonly ICallable _compare; private readonly JsValue[] _comparableArray = new JsValue[2]; private TypedArrayComparer(ArrayBufferInstance buffer, ICallable compare) { _buffer = buffer; _compare = compare; } public int Compare(JsValue x, JsValue y) { if (_compare is not null) { _comparableArray[0] = x; _comparableArray[1] = y; var v = TypeConverter.ToNumber(_compare.Call(Undefined, _comparableArray)); _buffer.AssertNotDetached(); if (double.IsNaN(v)) { return 0; } return (int) v; } if (x.Type == Types.BigInt || y.Type == Types.BigInt) { var xBigInt = TypeConverter.ToBigInt(x); var yBigInt = TypeConverter.ToBigInt(y); return xBigInt.CompareTo(yBigInt); } var xValue = x.AsNumber(); var yValue = y.AsNumber(); if (double.IsNaN(xValue) && double.IsNaN(yValue)) { return 0; } if (double.IsNaN(xValue)) { return 1; } if (double.IsNaN(yValue)) { return -1; } if (xValue < yValue) { return -1; } if (xValue > yValue) { return 1; } if (NumberInstance.IsNegativeZero(xValue) && yValue == 0) { return -1; } if (xValue == 0 && NumberInstance.IsNegativeZero(yValue)) { return 1; } return 0; } } } }