Bug 1690111 - Use new TypedArray APIs for processing data. r=farre,media-playback-reviewers,padenot,chunmin,sfink

Depends on D152497

Differential Revision: https://phabricator.services.mozilla.com/D152498
This commit is contained in:
Peter Van der Beken 2023-09-20 09:42:02 +00:00
parent c49b5c3f78
commit 627ac90e0c
44 changed files with 1201 additions and 1174 deletions

View File

@ -117,29 +117,17 @@ void ChromeUtils::Base64URLEncode(GlobalObject& aGlobal,
const ArrayBufferViewOrArrayBuffer& aSource,
const Base64URLEncodeOptions& aOptions,
nsACString& aResult, ErrorResult& aRv) {
size_t length = 0;
uint8_t* data = nullptr;
if (aSource.IsArrayBuffer()) {
const ArrayBuffer& buffer = aSource.GetAsArrayBuffer();
buffer.ComputeState();
length = buffer.Length();
data = buffer.Data();
} else if (aSource.IsArrayBufferView()) {
const ArrayBufferView& view = aSource.GetAsArrayBufferView();
view.ComputeState();
length = view.Length();
data = view.Data();
} else {
MOZ_CRASH("Uninitialized union: expected buffer or view");
}
auto paddingPolicy = aOptions.mPad ? Base64URLEncodePaddingPolicy::Include
: Base64URLEncodePaddingPolicy::Omit;
nsresult rv = mozilla::Base64URLEncode(length, data, paddingPolicy, aResult);
if (NS_WARN_IF(NS_FAILED(rv))) {
aResult.Truncate();
aRv.Throw(rv);
}
ProcessTypedArrays(
aSource, [&](const Span<uint8_t>& aData, JS::AutoCheckCannotGC&&) {
nsresult rv = mozilla::Base64URLEncode(aData.Length(), aData.Elements(),
paddingPolicy, aResult);
if (NS_WARN_IF(NS_FAILED(rv))) {
aResult.Truncate();
aRv.Throw(rv);
}
});
}
/* static */

View File

@ -8,6 +8,7 @@
#include "js/TypeDecls.h"
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/dom/CompressionStreamBinding.h"
#include "mozilla/dom/ReadableStream.h"
#include "mozilla/dom/WritableStream.h"
@ -57,16 +58,21 @@ class CompressionStreamAlgorithms : public TransformerAlgorithmsWrapper {
// https://wicg.github.io/compression/#compress-and-enqueue-a-chunk
// Step 1: If chunk is not a BufferSource type, then throw a TypeError.
// (ExtractSpanFromBufferSource does it)
Span<const uint8_t> input = ExtractSpanFromBufferSource(cx, aChunk, aRv);
if (aRv.Failed()) {
RootedUnion<OwningArrayBufferViewOrArrayBuffer> bufferSource(cx);
if (!bufferSource.Init(cx, aChunk)) {
aRv.MightThrowJSException();
aRv.StealExceptionFromJSContext(cx);
return;
}
// Step 2: Let buffer be the result of compressing chunk with cs's format
// and context.
// Step 3 - 5: (Done in CompressAndEnqueue)
CompressAndEnqueue(cx, input, ZLibFlush::No, aController, aRv);
ProcessTypedArraysFixed(
bufferSource,
[&](const Span<uint8_t>& aData) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
CompressAndEnqueue(cx, aData, ZLibFlush::No, aController, aRv);
});
}
// Step 4 of

View File

@ -40,11 +40,9 @@ JSObject* Crypto::WrapObject(JSContext* aCx,
void Crypto::GetRandomValues(JSContext* aCx, const ArrayBufferView& aArray,
JS::MutableHandle<JSObject*> aRetval,
ErrorResult& aRv) {
JS::Rooted<JSObject*> view(aCx, aArray.Obj());
// Throw if the wrong type of ArrayBufferView is passed in
// (Part of the Web Crypto API spec)
switch (JS_GetArrayBufferViewType(view)) {
switch (aArray.Type()) {
case js::Scalar::Int8:
case js::Scalar::Uint8:
case js::Scalar::Uint8Clamped:
@ -60,17 +58,6 @@ void Crypto::GetRandomValues(JSContext* aCx, const ArrayBufferView& aArray,
return;
}
aArray.ComputeState();
uint32_t dataLen = aArray.Length();
if (dataLen == 0) {
NS_WARNING("ArrayBufferView length is 0, cannot continue");
aRetval.set(view);
return;
} else if (dataLen > 65536) {
aRv.Throw(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR);
return;
}
nsCOMPtr<nsIRandomGenerator> randomGenerator =
do_GetService("@mozilla.org/security/random-generator;1");
if (!randomGenerator) {
@ -78,14 +65,27 @@ void Crypto::GetRandomValues(JSContext* aCx, const ArrayBufferView& aArray,
return;
}
nsresult rv =
randomGenerator->GenerateRandomBytesInto(aArray.Data(), dataLen);
if (NS_FAILED(rv)) {
aRv.Throw(NS_ERROR_DOM_OPERATION_ERR);
return;
}
aArray.ProcessFixedData([&](const Span<uint8_t>& aData) {
if (aData.Length() == 0) {
NS_WARNING("ArrayBufferView length is 0, cannot continue");
aRetval.set(aArray.Obj());
return;
}
aRetval.set(view);
if (aData.Length() > 65536) {
aRv.Throw(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR);
return;
}
nsresult rv = randomGenerator->GenerateRandomBytesInto(aData.Elements(),
aData.Length());
if (NS_FAILED(rv)) {
aRv.Throw(NS_ERROR_DOM_OPERATION_ERR);
return;
}
aRetval.set(aArray.Obj());
});
}
void Crypto::RandomUUID(nsACString& aRetVal) {

View File

@ -199,29 +199,33 @@ already_AddRefed<DOMMatrixReadOnly> DOMMatrixReadOnly::FromMatrix(
already_AddRefed<DOMMatrixReadOnly> DOMMatrixReadOnly::FromFloat32Array(
const GlobalObject& aGlobal, const Float32Array& aArray32,
ErrorResult& aRv) {
aArray32.ComputeState();
const int length = aArray32.Length();
const bool is2D = length == 6;
RefPtr<DOMMatrixReadOnly> obj =
new DOMMatrixReadOnly(aGlobal.GetAsSupports(), is2D);
SetDataInMatrix(obj, aArray32.Data(), length, aRv);
return obj.forget();
nsCOMPtr<nsISupports> global = aGlobal.GetAsSupports();
return aArray32.ProcessData(
[&](const Span<float>& aData, JS::AutoCheckCannotGC&& nogc) {
const int length = aData.Length();
const bool is2D = length == 6;
RefPtr<DOMMatrixReadOnly> obj =
new DOMMatrixReadOnly(global.forget(), is2D);
SetDataInMatrix(obj, aData.Elements(), length, aRv);
nogc.reset(); // Done with aData
return obj.forget();
});
}
already_AddRefed<DOMMatrixReadOnly> DOMMatrixReadOnly::FromFloat64Array(
const GlobalObject& aGlobal, const Float64Array& aArray64,
ErrorResult& aRv) {
aArray64.ComputeState();
const int length = aArray64.Length();
const bool is2D = length == 6;
RefPtr<DOMMatrixReadOnly> obj =
new DOMMatrixReadOnly(aGlobal.GetAsSupports(), is2D);
SetDataInMatrix(obj, aArray64.Data(), length, aRv);
return obj.forget();
nsCOMPtr<nsISupports> global = aGlobal.GetAsSupports();
return aArray64.ProcessData(
[&](const Span<double>& aData, JS::AutoCheckCannotGC&& nogc) {
const int length = aData.Length();
const bool is2D = length == 6;
RefPtr<DOMMatrixReadOnly> obj =
new DOMMatrixReadOnly(global.forget(), is2D);
SetDataInMatrix(obj, aData.Elements(), length, aRv);
nogc.reset(); // Done with aData
return obj.forget();
});
}
already_AddRefed<DOMMatrixReadOnly> DOMMatrixReadOnly::Constructor(
@ -642,27 +646,31 @@ already_AddRefed<DOMMatrix> DOMMatrix::FromMatrix(
already_AddRefed<DOMMatrix> DOMMatrix::FromFloat32Array(
const GlobalObject& aGlobal, const Float32Array& aArray32,
ErrorResult& aRv) {
aArray32.ComputeState();
const int length = aArray32.Length();
const bool is2D = length == 6;
RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports(), is2D);
SetDataInMatrix(obj, aArray32.Data(), length, aRv);
return obj.forget();
nsCOMPtr<nsISupports> global = aGlobal.GetAsSupports();
return aArray32.ProcessData(
[&](const Span<float>& aData, JS::AutoCheckCannotGC&& nogc) {
const int length = aData.Length();
const bool is2D = length == 6;
RefPtr<DOMMatrix> obj = new DOMMatrix(global.forget(), is2D);
SetDataInMatrix(obj, aData.Elements(), length, aRv);
nogc.reset(); // Done with aData
return obj.forget();
});
}
already_AddRefed<DOMMatrix> DOMMatrix::FromFloat64Array(
const GlobalObject& aGlobal, const Float64Array& aArray64,
ErrorResult& aRv) {
aArray64.ComputeState();
const int length = aArray64.Length();
const bool is2D = length == 6;
RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports(), is2D);
SetDataInMatrix(obj, aArray64.Data(), length, aRv);
return obj.forget();
nsCOMPtr<nsISupports> global = aGlobal.GetAsSupports();
return aArray64.ProcessData(
[&](const Span<double>& aData, JS::AutoCheckCannotGC&& nogc) {
const int length = aData.Length();
const bool is2D = length == 6;
RefPtr<DOMMatrix> obj = new DOMMatrix(global.forget(), is2D);
SetDataInMatrix(obj, aData.Elements(), length, aRv);
nogc.reset(); // Done with aData
return obj.forget();
});
}
already_AddRefed<DOMMatrix> DOMMatrix::Constructor(

View File

@ -254,7 +254,10 @@ class DOMMatrixReadOnly : public nsWrapperCache {
DOMMatrixReadOnly* SetMatrixValue(const nsACString&, ErrorResult&);
void Ensure3DMatrix();
DOMMatrixReadOnly(nsISupports* aParent, bool is2D) : mParent(aParent) {
DOMMatrixReadOnly(nsISupports* aParent, bool is2D)
: DOMMatrixReadOnly(do_AddRef(aParent), is2D) {}
DOMMatrixReadOnly(already_AddRefed<nsISupports>&& aParent, bool is2D)
: mParent(std::move(aParent)) {
if (is2D) {
mMatrix2D = MakeUnique<gfx::MatrixDouble>();
} else {
@ -335,6 +338,8 @@ class DOMMatrix : public DOMMatrixReadOnly {
private:
DOMMatrix(nsISupports* aParent, bool is2D)
: DOMMatrixReadOnly(aParent, is2D) {}
DOMMatrix(already_AddRefed<nsISupports>&& aParent, bool is2D)
: DOMMatrixReadOnly(std::move(aParent), is2D) {}
};
} // namespace dom

View File

@ -120,8 +120,9 @@ already_AddRefed<Document> DOMParser::ParseFromSafeString(const nsAString& aStr,
already_AddRefed<Document> DOMParser::ParseFromBuffer(const Uint8Array& aBuf,
SupportedType aType,
ErrorResult& aRv) {
aBuf.ComputeState();
return ParseFromBuffer(Span(aBuf.Data(), aBuf.Length()), aType, aRv);
return aBuf.ProcessFixedData([&](const Span<uint8_t>& aData) {
return ParseFromBuffer(aData, aType, aRv);
});
}
already_AddRefed<Document> DOMParser::ParseFromBuffer(Span<const uint8_t> aBuf,

View File

@ -14,6 +14,7 @@
#include "mozilla/dom/TextDecoderStream.h"
#include "mozilla/dom/TransformStream.h"
#include "mozilla/dom/TransformerCallbackHelpers.h"
#include "mozilla/dom/UnionTypes.h"
#include "ZLibHelper.h"
@ -54,16 +55,21 @@ class DecompressionStreamAlgorithms : public TransformerAlgorithmsWrapper {
// https://wicg.github.io/compression/#compress-and-enqueue-a-chunk
// Step 1: If chunk is not a BufferSource type, then throw a TypeError.
// (ExtractSpanFromBufferSource does it)
Span<const uint8_t> input = ExtractSpanFromBufferSource(cx, aChunk, aRv);
if (aRv.Failed()) {
RootedUnion<OwningArrayBufferViewOrArrayBuffer> bufferSource(cx);
if (!bufferSource.Init(cx, aChunk)) {
aRv.MightThrowJSException();
aRv.StealExceptionFromJSContext(cx);
return;
}
// Step 2: Let buffer be the result of decompressing chunk with ds's format
// and context. If this results in an error, then throw a TypeError.
// Step 3 - 5: (Done in CompressAndEnqueue)
DecompressAndEnqueue(cx, input, ZLibFlush::No, aController, aRv);
ProcessTypedArraysFixed(
bufferSource,
[&](const Span<uint8_t>& aData) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
DecompressAndEnqueue(cx, aData, ZLibFlush::No, aController, aRv);
});
}
// Step 4 of

View File

@ -415,18 +415,6 @@ struct TypedArray_base : public SpiderMonkeyInterfaceObjectStorage,
return mData;
}
// Return a pointer to data that will not move during a GC.
//
// For some smaller views, this will copy the data into the provided buffer
// and return that buffer as the pointer. Otherwise, this will return a
// direct pointer to the actual data with no copying. If the provided buffer
// is not large enough, nullptr will be returned. If bufSize is at least
// JS_MaxMovableTypedArraySize(), the data is guaranteed to fit.
inline element_type* FixedData(uint8_t* buffer, size_t bufSize) const {
MOZ_ASSERT(mComputed);
return JS_GetArrayBufferViewFixedData(mImplObj, buffer, bufSize);
}
inline uint32_t Length() const {
MOZ_ASSERT(mComputed);
return mLength;
@ -714,6 +702,7 @@ struct TypedArray_base : public SpiderMonkeyInterfaceObjectStorage,
return CallProcessor(GetCurrentData(), std::forward<Processor>(aProcessor));
}
public:
inline void Reset() {
// This method mostly exists to inform the GC rooting hazard analysis that
// the variable can be considered dead, at least until you do anything else

View File

@ -5907,8 +5907,7 @@ nsresult CanvasRenderingContext2D::GetImageDataArray(
nsIPrincipal& aSubjectPrincipal, JSObject** aRetval) {
MOZ_ASSERT(aWidth && aHeight);
// Restrict the typed array length to INT32_MAX because that's all we support
// in dom::TypedArray::ComputeState.
// Restrict the typed array length to INT32_MAX because that's all we support.
CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aWidth) * aHeight * 4;
if (!len.isValid() || len.value() > INT32_MAX) {
return NS_ERROR_DOM_INDEX_SIZE_ERR;
@ -6137,18 +6136,13 @@ void CanvasRenderingContext2D::PutImageData_explicit(
return;
}
arr.ComputeState();
RefPtr<DataSourceSurface> sourceSurface;
uint8_t* lockedBits = nullptr;
uint32_t dataLen = arr.Length();
uint32_t len = width * height * 4;
if (dataLen != len) {
return aRv.ThrowInvalidStateError("Invalid width or height");
}
// The canvas spec says that the current path, transformation matrix, shadow
// attributes, global alpha, the clipping region, and global composition
// operator must not affect the getImageData() and putImageData() methods.
// The canvas spec says that the current path, transformation matrix,
// shadow attributes, global alpha, the clipping region, and global
// composition operator must not affect the getImageData() and
// putImageData() methods.
const gfx::Rect putRect(dirtyRect);
EnsureTarget(&putRect);
@ -6157,8 +6151,6 @@ void CanvasRenderingContext2D::PutImageData_explicit(
}
DataSourceSurface::MappedSurface map;
RefPtr<DataSourceSurface> sourceSurface;
uint8_t* lockedBits = nullptr;
uint8_t* dstData;
IntSize dstSize;
int32_t dstStride;
@ -6169,10 +6161,11 @@ void CanvasRenderingContext2D::PutImageData_explicit(
sourceSurface = Factory::CreateDataSourceSurface(
dirtyRect.Size(), SurfaceFormat::B8G8R8A8, false);
// In certain scenarios, requesting larger than 8k image fails. Bug 803568
// covers the details of how to run into it, but the full detailed
// investigation hasn't been done to determine the underlying cause. We
// will just handle the failure to allocate the surface to avoid a crash.
// In certain scenarios, requesting larger than 8k image fails. Bug
// 803568 covers the details of how to run into it, but the full
// detailed investigation hasn't been done to determine the
// underlying cause. We will just handle the failure to allocate
// the surface to avoid a crash.
if (!sourceSurface) {
return aRv.Throw(NS_ERROR_FAILURE);
}
@ -6188,12 +6181,27 @@ void CanvasRenderingContext2D::PutImageData_explicit(
dstFormat = sourceSurface->GetFormat();
}
uint8_t* srcData = arr.Data() + srcRect.y * (width * 4) + srcRect.x * 4;
arr.ProcessData(
[&](const Span<uint8_t>& aData, JS::AutoCheckCannotGC&& nogc) {
// Verify that the length hasn't changed.
if (aData.Length() != width * height * 4) {
// FIXME Should this call ReleaseBits/Unmap?
return aRv.ThrowInvalidStateError("Invalid width or height");
}
PremultiplyData(
srcData, width * 4, SurfaceFormat::R8G8B8A8, dstData, dstStride,
mOpaque ? SurfaceFormat::X8R8G8B8_UINT32 : SurfaceFormat::A8R8G8B8_UINT32,
dirtyRect.Size());
uint8_t* srcData =
aData.Elements() + srcRect.y * (width * 4) + srcRect.x * 4;
PremultiplyData(srcData, width * 4, SurfaceFormat::R8G8B8A8, dstData,
dstStride,
mOpaque ? SurfaceFormat::X8R8G8B8_UINT32
: SurfaceFormat::A8R8G8B8_UINT32,
dirtyRect.Size());
});
if (aRv.Failed()) {
return;
}
if (lockedBits) {
mTarget->ReleaseBits(lockedBits);

View File

@ -14,6 +14,7 @@
#include "js/ScalarType.h" // js::Scalar::Type
#include "mozilla/dom/Document.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/dom/WebGLContextEvent.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/EnumeratedRange.h"
@ -434,6 +435,44 @@ void ClientWebGLContext::Run(Args&&... args) const {
webgl::Serialize(destBytes, id, args...);
}
template <typename MethodType, MethodType method, typename... Args>
void ClientWebGLContext::RunWithGCData(JS::AutoCheckCannotGC&& aNoGC,
Args&&... args) const {
// Hold a strong-ref to prevent LoseContext=>UAF.
//
// Note that `aNoGC` must be reset after the GC data is done being used and
// before the `notLost` destructor runs, since it could GC.
const auto notLost = mNotLost;
if (IsContextLost()) {
aNoGC.reset(); // GC data will not be used.
return;
}
const auto& inProcess = notLost->inProcess;
if (inProcess) {
(inProcess.get()->*method)(std::forward<Args>(args)...);
aNoGC.reset(); // Done with any GC data
return;
}
const auto& child = notLost->outOfProcess;
const auto id = IdByMethod<MethodType, method>();
const auto info = webgl::SerializationInfo(id, args...);
const auto maybeDest = child->AllocPendingCmdBytes(info.requiredByteCount,
info.alignmentOverhead);
if (!maybeDest) {
aNoGC.reset(); // GC data will not be used.
JsWarning("Failed to allocate internal command buffer.");
OnContextLoss(webgl::ContextLossReason::None);
return;
}
const auto& destBytes = *maybeDest;
webgl::Serialize(destBytes, id, args...);
aNoGC.reset(); // Done with any GC data
}
// -------------------------------------------------------------------------
// Client-side helper methods. Dispatch to a Host method.
// -------------------------------------------------------------------------
@ -2873,13 +2912,14 @@ void ClientWebGLContext::Clear(GLbitfield mask) {
void ClientWebGLContext::ClearBufferTv(const GLenum buffer,
const GLint drawBuffer,
const webgl::AttribBaseType type,
const Range<const uint8_t>& view,
JS::AutoCheckCannotGC&& nogc,
const Span<const uint8_t>& view,
const GLuint srcElemOffset) {
const FuncScope funcScope(*this, "clearBufferu?[fi]v");
if (IsContextLost()) return;
const auto byteOffset = CheckedInt<size_t>(srcElemOffset) * sizeof(float);
if (!byteOffset.isValid() || byteOffset.value() > view.length()) {
if (!byteOffset.isValid() || byteOffset.value() > view.Length()) {
nogc.reset();
EnqueueError(LOCAL_GL_INVALID_VALUE, "`srcOffset` too large for `values`.");
return;
}
@ -2900,17 +2940,20 @@ void ClientWebGLContext::ClearBufferTv(const GLenum buffer,
break;
default:
nogc.reset();
EnqueueError_ArgEnum("buffer", buffer);
return;
}
const auto requiredBytes = byteOffset + dataSize;
if (!requiredBytes.isValid() || requiredBytes.value() > view.length()) {
if (!requiredBytes.isValid() || requiredBytes.value() > view.Length()) {
nogc.reset();
EnqueueError(LOCAL_GL_INVALID_VALUE, "`values` too small.");
return;
}
memcpy(data.data.data(), view.begin().get() + byteOffset.value(), dataSize);
memcpy(data.data.data(), view.data() + byteOffset.value(), dataSize);
nogc.reset(); // Done with `view`.
Run<RPROC(ClearBufferTv)>(buffer, drawBuffer, data);
AfterDrawCall();
@ -3356,6 +3399,14 @@ void ClientWebGLContext::BindBufferRangeImpl(const GLenum target,
size);
}
static inline size_t SizeOfViewElem(const dom::ArrayBufferView& view) {
const auto& elemType = view.Type();
if (elemType == js::Scalar::MaxTypedArrayViewType) // DataViews.
return 1;
return js::Scalar::byteSize(elemType);
}
void ClientWebGLContext::GetBufferSubData(GLenum target, GLintptr srcByteOffset,
const dom::ArrayBufferView& dstData,
GLuint dstElemOffset,
@ -3366,42 +3417,44 @@ void ClientWebGLContext::GetBufferSubData(GLenum target, GLintptr srcByteOffset,
mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
if (!ValidateNonNegative("srcByteOffset", srcByteOffset)) return;
uint8_t* bytes;
size_t byteLen;
if (!ValidateArrayBufferView(dstData, dstElemOffset, dstElemCountOverride,
LOCAL_GL_INVALID_VALUE, &bytes, &byteLen)) {
return;
}
const auto destView = Range<uint8_t>{bytes, byteLen};
size_t elemSize = SizeOfViewElem(dstData);
dstData.ProcessFixedData([&](const Span<uint8_t>& aData) {
const auto& destView =
ValidateArrayBufferView(aData, elemSize, dstElemOffset,
dstElemCountOverride, LOCAL_GL_INVALID_VALUE);
if (!destView) {
return;
}
const auto& inProcessContext = notLost->inProcess;
if (inProcessContext) {
inProcessContext->GetBufferSubData(target, srcByteOffset, destView);
return;
}
const auto& inProcessContext = notLost->inProcess;
if (inProcessContext) {
inProcessContext->GetBufferSubData(target, srcByteOffset, *destView);
return;
}
const auto& child = notLost->outOfProcess;
child->FlushPendingCmds();
mozilla::ipc::Shmem rawShmem;
if (!child->SendGetBufferSubData(target, srcByteOffset, destView.length(),
&rawShmem)) {
return;
}
const webgl::RaiiShmem shmem{child, rawShmem};
if (!shmem) {
EnqueueError(LOCAL_GL_OUT_OF_MEMORY, "Failed to map in sub data buffer.");
return;
}
const auto& child = notLost->outOfProcess;
child->FlushPendingCmds();
mozilla::ipc::Shmem rawShmem;
if (!child->SendGetBufferSubData(target, srcByteOffset, destView->length(),
&rawShmem)) {
return;
}
const webgl::RaiiShmem shmem{child, rawShmem};
if (!shmem) {
EnqueueError(LOCAL_GL_OUT_OF_MEMORY, "Failed to map in sub data buffer.");
return;
}
const auto shmemView = shmem.ByteRange();
MOZ_RELEASE_ASSERT(shmemView.length() == 1 + destView.length());
const auto shmemView = shmem.ByteRange();
MOZ_RELEASE_ASSERT(shmemView.length() == 1 + destView->length());
const auto ok = bool(*(shmemView.begin().get()));
const auto srcView =
Range<const uint8_t>{shmemView.begin() + 1, shmemView.end()};
if (ok) {
Memcpy(destView.begin(), srcView.begin(), srcView.length());
}
const auto ok = bool(*(shmemView.begin().get()));
const auto srcView =
Range<const uint8_t>{shmemView.begin() + 1, shmemView.end()};
if (ok) {
Memcpy(destView->begin(), srcView.begin(), srcView.length());
}
});
}
////
@ -3428,9 +3481,9 @@ void ClientWebGLContext::BufferData(
if (!ValidateNonNull("src", maybeSrc)) return;
const auto& src = maybeSrc.Value();
src.ComputeState();
const auto range = Range<const uint8_t>{src.Data(), src.Length()};
Run<RPROC(BufferData)>(target, RawBuffer<>(range), usage);
src.ProcessFixedData([&](const Span<uint8_t>& aData) {
Run<RPROC(BufferData)>(target, RawBuffer<>(aData), usage);
});
}
void ClientWebGLContext::BufferData(GLenum target,
@ -3438,14 +3491,16 @@ void ClientWebGLContext::BufferData(GLenum target,
GLenum usage, GLuint srcElemOffset,
GLuint srcElemCountOverride) {
const FuncScope funcScope(*this, "bufferData");
uint8_t* bytes;
size_t byteLen;
if (!ValidateArrayBufferView(src, srcElemOffset, srcElemCountOverride,
LOCAL_GL_INVALID_VALUE, &bytes, &byteLen)) {
return;
}
const auto range = Range<const uint8_t>{bytes, byteLen};
Run<RPROC(BufferData)>(target, RawBuffer<>(range), usage);
size_t elemSize = SizeOfViewElem(src);
src.ProcessFixedData([&](const Span<uint8_t>& aData) {
const auto& range =
ValidateArrayBufferView(aData, elemSize, srcElemOffset,
srcElemCountOverride, LOCAL_GL_INVALID_VALUE);
if (!range) {
return;
}
Run<RPROC(BufferData)>(target, RawBuffer<>(*range), usage);
});
}
void ClientWebGLContext::RawBufferData(GLenum target, const uint8_t* srcBytes,
@ -3473,10 +3528,10 @@ void ClientWebGLContext::BufferSubData(GLenum target,
WebGLsizeiptr dstByteOffset,
const dom::ArrayBuffer& src) {
const FuncScope funcScope(*this, "bufferSubData");
src.ComputeState();
const auto range = Range<const uint8_t>{src.Data(), src.Length()};
Run<RPROC(BufferSubData)>(target, dstByteOffset, RawBuffer<>(range),
/* unsynchronized */ false);
src.ProcessFixedData([&](const Span<uint8_t>& aData) {
Run<RPROC(BufferSubData)>(target, dstByteOffset, RawBuffer<>(aData),
/* unsynchronized */ false);
});
}
void ClientWebGLContext::BufferSubData(GLenum target,
@ -3485,15 +3540,17 @@ void ClientWebGLContext::BufferSubData(GLenum target,
GLuint srcElemOffset,
GLuint srcElemCountOverride) {
const FuncScope funcScope(*this, "bufferSubData");
uint8_t* bytes;
size_t byteLen;
if (!ValidateArrayBufferView(src, srcElemOffset, srcElemCountOverride,
LOCAL_GL_INVALID_VALUE, &bytes, &byteLen)) {
return;
}
const auto range = Range<const uint8_t>{bytes, byteLen};
Run<RPROC(BufferSubData)>(target, dstByteOffset, RawBuffer<>(range),
/* unsynchronized */ false);
size_t elemSize = SizeOfViewElem(src);
src.ProcessFixedData([&](const Span<uint8_t>& aData) {
const auto& range =
ValidateArrayBufferView(aData, elemSize, srcElemOffset,
srcElemCountOverride, LOCAL_GL_INVALID_VALUE);
if (!range) {
return;
}
Run<RPROC(BufferSubData)>(target, dstByteOffset, RawBuffer<>(*range),
/* unsynchronized */ false);
});
}
void ClientWebGLContext::CopyBufferSubData(GLenum readTarget,
@ -4082,19 +4139,11 @@ Range<T> SubRange(const Range<T>& full, const size_t offset,
return Range<T>{newBegin, newBegin + length};
}
static inline size_t SizeOfViewElem(const dom::ArrayBufferView& view) {
const auto& elemType = view.Type();
if (elemType == js::Scalar::MaxTypedArrayViewType) // DataViews.
return 1;
return js::Scalar::byteSize(elemType);
}
Maybe<Range<const uint8_t>> GetRangeFromView(const dom::ArrayBufferView& view,
Maybe<Range<const uint8_t>> GetRangeFromData(const Span<uint8_t>& data,
size_t bytesPerElem,
GLuint elemOffset,
GLuint elemCountOverride) {
const auto byteRange = MakeRangeAbv(view); // In bytes.
const auto bytesPerElem = SizeOfViewElem(view);
const auto byteRange = Range(data); // In bytes.
auto elemCount = byteRange.length() / bytesPerElem;
if (elemOffset > elemCount) return {};
@ -4200,15 +4249,6 @@ void ClientWebGLContext::TexImage(uint8_t funcDims, GLenum imageTarget,
// -
// Demarcate the region within which GC is disallowed. Typed arrays can move
// their data during a GC, so this will allow the rooting hazard analysis to
// report if a GC is possible while any data pointers extracted from the
// typed array are still live.
dom::Uint8ClampedArray scopedArr;
const auto reset = MakeScopeExit([&] {
scopedArr.Reset(); // (For the hazard analysis) Done with the data.
});
// -
bool isDataUpload = false;
auto desc = [&]() -> Maybe<webgl::TexUnpackBlobDesc> {
@ -4240,17 +4280,23 @@ void ClientWebGLContext::TexImage(uint8_t funcDims, GLenum imageTarget,
break;
}
const auto range = GetRangeFromView(view, src.mViewElemOffset,
src.mViewElemLengthOverride);
if (!range) {
EnqueueError(LOCAL_GL_INVALID_OPERATION, "`source` too small.");
return {};
}
return Some(webgl::TexUnpackBlobDesc{imageTarget,
size.value(),
gfxAlphaType::NonPremult,
Some(RawBuffer<>{*range}),
{}});
return view.ProcessData(
[&](const Span<uint8_t>& aData,
JS::AutoCheckCannotGC&& nogc) -> Maybe<webgl::TexUnpackBlobDesc> {
const auto range = GetRangeFromData(aData, SizeOfViewElem(view),
src.mViewElemOffset,
src.mViewElemLengthOverride);
if (!range) {
nogc.reset();
EnqueueError(LOCAL_GL_INVALID_OPERATION, "`source` too small.");
return {};
}
return Some(webgl::TexUnpackBlobDesc{imageTarget,
size.value(),
gfxAlphaType::NonPremult,
Some(RawBuffer<>{*range}),
{}});
});
}
if (src.mImageBitmap) {
@ -4260,50 +4306,59 @@ void ClientWebGLContext::TexImage(uint8_t funcDims, GLenum imageTarget,
if (src.mImageData) {
const auto& imageData = *src.mImageData;
dom::Uint8ClampedArray scopedArr;
MOZ_RELEASE_ASSERT(scopedArr.Init(imageData.GetDataObject()));
scopedArr.ComputeState();
const auto dataSize = scopedArr.Length();
const auto data = reinterpret_cast<uint8_t*>(scopedArr.Data());
if (!data) {
// Neutered, e.g. via Transfer
EnqueueError(LOCAL_GL_INVALID_VALUE,
"ImageData.data.buffer is Detached. (Maybe you Transfered "
"it to a Worker?");
return {};
}
// -
return scopedArr.ProcessData(
[&](const Span<uint8_t>& aData,
JS::AutoCheckCannotGC&& nogc) -> Maybe<webgl::TexUnpackBlobDesc> {
const auto dataSize = aData.Length();
const auto data = aData.Elements();
if (dataSize == 0) {
nogc.reset(); // aData will not be used.
EnqueueError(
LOCAL_GL_INVALID_VALUE,
"ImageData.data.buffer is Detached. (Maybe you Transfered "
"it to a Worker?");
return {};
}
const gfx::IntSize imageSize(imageData.Width(), imageData.Height());
const auto sizeFromDims =
CheckedInt<size_t>(imageSize.width) * imageSize.height * 4;
MOZ_RELEASE_ASSERT(sizeFromDims.isValid() &&
sizeFromDims.value() == dataSize);
// -
const RefPtr<gfx::DataSourceSurface> surf =
gfx::Factory::CreateWrappingDataSourceSurface(
data, imageSize.width * 4, imageSize,
gfx::SurfaceFormat::R8G8B8A8);
MOZ_ASSERT(surf);
const gfx::IntSize imageSize(imageData.Width(), imageData.Height());
const auto sizeFromDims =
CheckedInt<size_t>(imageSize.width) * imageSize.height * 4;
MOZ_RELEASE_ASSERT(sizeFromDims.isValid() &&
sizeFromDims.value() == dataSize);
// -
const RefPtr<gfx::DataSourceSurface> surf =
gfx::Factory::CreateWrappingDataSourceSurface(
data, imageSize.width * 4, imageSize,
gfx::SurfaceFormat::R8G8B8A8);
MOZ_ASSERT(surf);
const auto imageUSize = *uvec2::FromSize(imageSize);
const auto concreteSize =
size.valueOr(uvec3{imageUSize.x, imageUSize.y, 1});
// -
// WhatWG "HTML Living Standard" (30 October 2015):
// "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned
// as non-premultiplied alpha values."
return Some(webgl::TexUnpackBlobDesc{imageTarget,
concreteSize,
gfxAlphaType::NonPremult,
{},
{},
Some(imageUSize),
nullptr,
{},
surf});
const auto imageUSize = *uvec2::FromSize(imageSize);
const auto concreteSize =
size.valueOr(uvec3{imageUSize.x, imageUSize.y, 1});
// WhatWG "HTML Living Standard" (30 October 2015):
// "The getImageData(sx, sy, sw, sh) method [...] Pixels must be
// returned as non-premultiplied alpha values."
auto result =
Some(webgl::TexUnpackBlobDesc{imageTarget,
concreteSize,
gfxAlphaType::NonPremult,
{},
{},
Some(imageUSize),
nullptr,
{},
surf});
nogc.reset(); // Done with aData
return result;
});
}
if (src.mOffscreenCanvas) {
@ -4563,29 +4618,40 @@ void ClientWebGLContext::CompressedTexImage(bool sub, uint8_t funcDims,
return;
}
RawBuffer<> range;
Maybe<uint64_t> pboOffset;
if (src.mView) {
const auto maybe = GetRangeFromView(*src.mView, src.mViewElemOffset,
src.mViewElemLengthOverride);
if (!maybe) {
EnqueueError(LOCAL_GL_INVALID_VALUE, "`source` too small.");
src.mView->ProcessData([&](const Span<uint8_t>& aData,
JS::AutoCheckCannotGC&& aNoGC) {
const auto range =
GetRangeFromData(aData, SizeOfViewElem(*src.mView),
src.mViewElemOffset, src.mViewElemLengthOverride);
if (!range) {
aNoGC.reset();
EnqueueError(LOCAL_GL_INVALID_VALUE, "`source` too small.");
return;
}
// We don't need to shrink `range` because valid calls require
// `range` to match requirements exactly.
RunWithGCData<RPROC(CompressedTexImage)>(
std::move(aNoGC), sub, imageTarget, static_cast<uint32_t>(level),
format, CastUvec3(offset), CastUvec3(isize), RawBuffer<>{*range},
static_cast<uint32_t>(pboImageSize), Maybe<uint64_t>());
return;
}
range = RawBuffer<>{*maybe};
} else if (src.mPboOffset) {
if (!ValidateNonNegative("offset", *src.mPboOffset)) return;
pboOffset = Some(*src.mPboOffset);
} else {
});
return;
}
if (!src.mPboOffset) {
MOZ_CRASH("impossible");
}
// We don't need to shrink `range` because valid calls require `range` to
// match requirements exactly.
if (!ValidateNonNegative("offset", *src.mPboOffset)) {
return;
}
Run<RPROC(CompressedTexImage)>(
sub, imageTarget, static_cast<uint32_t>(level), format, CastUvec3(offset),
CastUvec3(isize), range, static_cast<uint32_t>(pboImageSize), pboOffset);
CastUvec3(isize), RawBuffer<>(), static_cast<uint32_t>(pboImageSize),
Some(*src.mPboOffset));
}
void ClientWebGLContext::CopyTexImage(uint8_t funcDims, GLenum imageTarget,
@ -4762,13 +4828,21 @@ void ClientWebGLContext::UniformData(const GLenum funcElemType,
const WebGLUniformLocationJS* const loc,
bool transpose,
const Range<const uint8_t>& bytes,
JS::AutoCheckCannotGC&& nogc,
GLuint elemOffset,
GLuint elemCountOverride) const {
// FuncScope::~FuncScope() can GC in a failure case, so all `return`
// statements need to `nogc.reset()` up until the `nogc` is consumed by
// `RunWithGCData`.
const FuncScope funcScope(*this, "uniform setter");
if (IsContextLost()) return;
if (IsContextLost()) {
nogc.reset();
return;
}
const auto& activeLinkResult = GetActiveLinkResult();
if (!activeLinkResult) {
nogc.reset();
EnqueueError(LOCAL_GL_INVALID_OPERATION, "No active linked Program.");
return;
}
@ -4777,12 +4851,14 @@ void ClientWebGLContext::UniformData(const GLenum funcElemType,
auto availCount = bytes.length() / sizeof(float);
if (elemOffset > availCount) {
nogc.reset();
EnqueueError(LOCAL_GL_INVALID_VALUE, "`elemOffset` too large for `data`.");
return;
}
availCount -= elemOffset;
if (elemCountOverride) {
if (elemCountOverride > availCount) {
nogc.reset();
EnqueueError(LOCAL_GL_INVALID_VALUE,
"`elemCountOverride` too large for `data`.");
return;
@ -4794,6 +4870,7 @@ void ClientWebGLContext::UniformData(const GLenum funcElemType,
const auto channels = ElemTypeComponents(funcElemType);
if (!availCount || availCount % channels != 0) {
nogc.reset();
EnqueueError(LOCAL_GL_INVALID_VALUE,
"`values` length (%u) must be a positive "
"integer multiple of size of %s.",
@ -4806,12 +4883,16 @@ void ClientWebGLContext::UniformData(const GLenum funcElemType,
uint32_t locId = -1;
if (MOZ_LIKELY(loc)) {
locId = loc->mLocation;
if (!loc->ValidateUsable(*this, "location")) return;
if (!loc->ValidateUsable(*this, "location")) {
nogc.reset();
return;
}
// -
const auto& reqLinkInfo = loc->mParent.lock();
if (reqLinkInfo.get() != activeLinkResult) {
nogc.reset();
EnqueueError(LOCAL_GL_INVALID_OPERATION,
"UniformLocation is not from the current active Program.");
return;
@ -4831,6 +4912,7 @@ void ClientWebGLContext::UniformData(const GLenum funcElemType,
}
validSetters.pop_back(); // Cheekily discard the extra trailing '/'.
nogc.reset();
EnqueueError(LOCAL_GL_INVALID_OPERATION,
"Uniform's `type` requires uniform setter of type %s.",
validSetters.c_str());
@ -4844,7 +4926,8 @@ void ClientWebGLContext::UniformData(const GLenum funcElemType,
reinterpret_cast<const webgl::UniformDataVal*>(bytes.begin().get()) +
elemOffset;
const auto range = Range{begin, availCount};
Run<RPROC(UniformData)>(locId, transpose, RawBuffer{range});
RunWithGCData<RPROC(UniformData)>(std::move(nogc), locId, transpose,
RawBuffer{range});
}
// -
@ -5044,21 +5127,20 @@ void ClientWebGLContext::ReadPixels(GLint x, GLint y, GLsizei width,
return;
}
uint8_t* bytes;
size_t byteLen;
if (!ValidateArrayBufferView(dstData, dstElemOffset, 0,
LOCAL_GL_INVALID_VALUE, &bytes, &byteLen)) {
return;
}
size_t elemSize = SizeOfViewElem(dstData);
dstData.ProcessFixedData([&](const Span<uint8_t>& aData) {
const auto& range = ValidateArrayBufferView(aData, elemSize, dstElemOffset,
0, LOCAL_GL_INVALID_VALUE);
if (!range) {
return;
}
const auto desc = webgl::ReadPixelsDesc{{x, y},
*uvec2::From(width, height),
{format, type},
state.mPixelPackState};
const auto range = Range<uint8_t>(bytes, byteLen);
if (!DoReadPixels(desc, range)) {
return;
}
const auto desc = webgl::ReadPixelsDesc{{x, y},
*uvec2::From(width, height),
{format, type},
state.mPixelPackState};
(void)DoReadPixels(desc, *range);
});
}
bool ClientWebGLContext::DoReadPixels(const webgl::ReadPixelsDesc& desc,
@ -6566,34 +6648,26 @@ const webgl::LinkResult& ClientWebGLContext::GetLinkResult(
// ---------------------------
bool ClientWebGLContext::ValidateArrayBufferView(
const dom::ArrayBufferView& view, GLuint elemOffset,
GLuint elemCountOverride, const GLenum errorEnum, uint8_t** const out_bytes,
size_t* const out_byteLen) const {
view.ComputeState();
uint8_t* const bytes = view.Data();
const size_t byteLen = view.Length();
const auto& elemSize = SizeOfViewElem(view);
size_t elemCount = byteLen / elemSize;
Maybe<Range<uint8_t>> ClientWebGLContext::ValidateArrayBufferView(
const Span<uint8_t>& bytes, size_t elemSize, GLuint elemOffset,
GLuint elemCountOverride, const GLenum errorEnum) const {
size_t elemCount = bytes.Length() / elemSize;
if (elemOffset > elemCount) {
EnqueueError(errorEnum, "Invalid offset into ArrayBufferView.");
return false;
return Nothing();
}
elemCount -= elemOffset;
if (elemCountOverride) {
if (elemCountOverride > elemCount) {
EnqueueError(errorEnum, "Invalid sub-length for ArrayBufferView.");
return false;
return Nothing();
}
elemCount = elemCountOverride;
}
*out_bytes = bytes + (elemOffset * elemSize);
*out_byteLen = elemCount * elemSize;
return true;
return Some(Range<uint8_t>(
bytes.Subspan(elemOffset * elemSize, elemCount * elemSize)));
}
// ---------------------------

View File

@ -7,9 +7,11 @@
#define CLIENTWEBGLCONTEXT_H_
#include "GLConsts.h"
#include "js/GCAPI.h"
#include "mozilla/dom/ImageData.h"
#include "mozilla/Range.h"
#include "mozilla/RefCounted.h"
#include "mozilla/dom/TypedArray.h"
#include "nsICanvasRenderingContextInternal.h"
#include "nsWrapperCache.h"
#include "mozilla/dom/WebGLRenderingContextBinding.h"
@ -24,6 +26,7 @@
#include "WebGLCommandQueue.h"
#include <memory>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <vector>
@ -618,22 +621,47 @@ using Float32ListU = dom::MaybeSharedFloat32ArrayOrUnrestrictedFloatSequence;
using Int32ListU = dom::MaybeSharedInt32ArrayOrLongSequence;
using Uint32ListU = dom::MaybeSharedUint32ArrayOrUnsignedLongSequence;
inline Range<const float> MakeRange(const Float32ListU& list) {
if (list.IsFloat32Array()) return MakeRangeAbv(list.GetAsFloat32Array());
return MakeRange(list.GetAsUnrestrictedFloatSequence());
template <typename Converter, typename T>
inline bool ConvertSequence(const dom::Sequence<T>& sequence,
Converter&& converter) {
// It's ok to GC here, but we need a common parameter list for
// Converter with the typed array version.
JS::AutoCheckCannotGC nogc;
nogc.reset();
return std::forward<Converter>(converter)(Span(sequence), std::move(nogc));
}
inline Range<const int32_t> MakeRange(const Int32ListU& list) {
if (list.IsInt32Array()) return MakeRangeAbv(list.GetAsInt32Array());
template <typename Converter>
inline bool Convert(const Float32ListU& list, Converter&& converter) {
if (list.IsFloat32Array()) {
return list.GetAsFloat32Array().ProcessData(
std::forward<Converter>(converter));
}
return MakeRange(list.GetAsLongSequence());
return ConvertSequence(list.GetAsUnrestrictedFloatSequence(),
std::forward<Converter>(converter));
}
inline Range<const uint32_t> MakeRange(const Uint32ListU& list) {
if (list.IsUint32Array()) return MakeRangeAbv(list.GetAsUint32Array());
template <typename Converter>
inline bool Convert(const Int32ListU& list, Converter&& converter) {
if (list.IsInt32Array()) {
return list.GetAsInt32Array().ProcessData(
std::forward<Converter>(converter));
}
return MakeRange(list.GetAsUnsignedLongSequence());
return ConvertSequence(list.GetAsLongSequence(),
std::forward<Converter>(converter));
}
template <typename Converter>
inline bool Convert(const Uint32ListU& list, Converter&& converter) {
if (list.IsUint32Array()) {
return list.GetAsUint32Array().ProcessData(
std::forward<Converter>(converter));
}
return ConvertSequence(list.GetAsUnsignedLongSequence(),
std::forward<Converter>(converter));
}
template <typename T>
@ -644,6 +672,11 @@ inline Range<const uint8_t> MakeByteRange(const T& x) {
typed.length() * sizeof(typed[0]));
}
template <typename T>
inline Range<const uint8_t> MakeByteRange(const Span<T>& x) {
return AsBytes(x);
}
// -
struct TexImageSourceAdapter final : public TexImageSource {
@ -898,11 +931,11 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
void EnqueueErrorImpl(GLenum errorOrZero, const nsACString&) const;
public:
bool ValidateArrayBufferView(const dom::ArrayBufferView& view,
GLuint elemOffset, GLuint elemCountOverride,
const GLenum errorEnum,
uint8_t** const out_bytes,
size_t* const out_byteLen) const;
Maybe<Range<uint8_t>> ValidateArrayBufferView(const Span<uint8_t>& bytes,
size_t elemSize,
GLuint elemOffset,
GLuint elemCountOverride,
const GLenum errorEnum) const;
protected:
template <typename T>
@ -1297,24 +1330,48 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
// -
private:
void ClearBufferTv(GLenum buffer, GLint drawBuffer, webgl::AttribBaseType,
const Range<const uint8_t>& view, GLuint srcElemOffset);
void ClearBufferTv(const GLenum buffer, const GLint drawBuffer,
const webgl::AttribBaseType type,
JS::AutoCheckCannotGC&& nogc,
const Span<const uint8_t>& view,
const GLuint srcElemOffset);
public:
void ClearBufferfv(GLenum buffer, GLint drawBuffer, const Float32ListU& list,
GLuint srcElemOffset) {
ClearBufferTv(buffer, drawBuffer, webgl::AttribBaseType::Float,
MakeByteRange(list), srcElemOffset);
const FuncScope funcScope(*this, "clearBufferfv");
if (!Convert(list, [&](const Span<const float>& aData,
JS::AutoCheckCannotGC&& nogc) {
ClearBufferTv(buffer, drawBuffer, webgl::AttribBaseType::Float,
std::move(nogc), AsBytes(aData), srcElemOffset);
return true;
})) {
EnqueueError(LOCAL_GL_INVALID_VALUE, "`values` too small.");
}
}
void ClearBufferiv(GLenum buffer, GLint drawBuffer, const Int32ListU& list,
GLuint srcElemOffset) {
ClearBufferTv(buffer, drawBuffer, webgl::AttribBaseType::Int,
MakeByteRange(list), srcElemOffset);
const FuncScope funcScope(*this, "clearBufferiv");
if (!Convert(list, [&](const Span<const int32_t>& aData,
JS::AutoCheckCannotGC&& nogc) {
ClearBufferTv(buffer, drawBuffer, webgl::AttribBaseType::Int,
std::move(nogc), AsBytes(aData), srcElemOffset);
return true;
})) {
EnqueueError(LOCAL_GL_INVALID_VALUE, "`values` too small.");
}
}
void ClearBufferuiv(GLenum buffer, GLint drawBuffer, const Uint32ListU& list,
GLuint srcElemOffset) {
ClearBufferTv(buffer, drawBuffer, webgl::AttribBaseType::Uint,
MakeByteRange(list), srcElemOffset);
const FuncScope funcScope(*this, "clearBufferuiv");
if (!Convert(list, [&](const Span<const uint32_t>& aData,
JS::AutoCheckCannotGC&& nogc) {
ClearBufferTv(buffer, drawBuffer, webgl::AttribBaseType::Uint,
std::move(nogc), AsBytes(aData), srcElemOffset);
return true;
})) {
EnqueueError(LOCAL_GL_INVALID_VALUE, "`values` too small.");
}
}
// -
@ -1840,9 +1897,26 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
return state.mActiveLinkResult.get();
}
// Used by callers that have a `nogc` token that ensures that no GC will
// happen while `bytes` is alive. Internally, the nogc range will be ended
// after `bytes` is used (either successfully but where the data are possibly
// sent over IPC which has a tendency to GC, or unsuccesfully in which case
// error handling can GC.)
void UniformData(GLenum funcElemType, const WebGLUniformLocationJS* const loc,
bool transpose, const Range<const uint8_t>& bytes,
GLuint elemOffset = 0, GLuint elemCountOverride = 0) const;
JS::AutoCheckCannotGC&& nogc, GLuint elemOffset = 0,
GLuint elemCountOverride = 0) const;
// Used by callers that are not passing `bytes` that might be GC-controlled.
// This will create an artificial and unnecessary nogc region that should
// get optimized away to nothing.
void UniformData(GLenum funcElemType, const WebGLUniformLocationJS* const loc,
bool transpose, const Range<const uint8_t>& bytes,
GLuint elemOffset = 0, GLuint elemCountOverride = 0) const {
JS::AutoCheckCannotGC nogc;
UniformData(funcElemType, loc, transpose, bytes, std::move(nogc),
elemOffset, elemCountOverride);
}
// -
@ -1898,12 +1972,15 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
// -
#define _(NT, TypeListU, TYPE) \
void Uniform##NT##v(const WebGLUniformLocationJS* const loc, \
const TypeListU& list, GLuint elemOffset = 0, \
GLuint elemCountOverride = 0) const { \
UniformData(TYPE, loc, false, MakeByteRange(list), elemOffset, \
elemCountOverride); \
#define _(NT, TypeListU, TYPE) \
void Uniform##NT##v(const WebGLUniformLocationJS* const loc, \
const TypeListU& list, GLuint elemOffset = 0, \
GLuint elemCountOverride = 0) const { \
Convert(list, [&](const auto& aData, JS::AutoCheckCannotGC&& nogc) { \
UniformData(TYPE, loc, false, MakeByteRange(aData), std::move(nogc), \
elemOffset, elemCountOverride); \
return true; \
}); \
}
_(1f, Float32ListU, LOCAL_GL_FLOAT)
@ -1927,8 +2004,12 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
void UniformMatrix##X##fv(const WebGLUniformLocationJS* loc, bool transpose, \
const Float32ListU& list, GLuint elemOffset = 0, \
GLuint elemCountOverride = 0) const { \
UniformData(LOCAL_GL_FLOAT_MAT##X, loc, transpose, MakeByteRange(list), \
elemOffset, elemCountOverride); \
Convert(list, [&](const Span<const float>& aData, \
JS::AutoCheckCannotGC&& nogc) { \
UniformData(LOCAL_GL_FLOAT_MAT##X, loc, transpose, MakeByteRange(aData), \
std::move(nogc), elemOffset, elemCountOverride); \
return true; \
}); \
}
_(2)
@ -1978,53 +2059,88 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
// -
template <typename List, typename T, size_t N>
bool MakeArrayFromList(const List& list, T (&array)[N]) {
bool badLength = false;
if (!Convert(list, [&](const auto& aData, JS::AutoCheckCannotGC&&) {
static_assert(
std::is_same_v<std::remove_const_t<
std::remove_reference_t<decltype(aData[0])>>,
T>);
if (N > aData.Length()) {
badLength = true;
return false;
}
std::copy_n(aData.begin(), N, array);
return true;
})) {
EnqueueError(
LOCAL_GL_INVALID_VALUE,
badLength
? nsPrintfCString("Length of `list` must be >=%zu.", N).get()
: "Conversion of `list` failed.");
return false;
}
return true;
}
void VertexAttrib1fv(const GLuint index, const Float32ListU& list) {
const FuncScope funcScope(*this, "vertexAttrib1fv");
if (IsContextLost()) return;
const auto range = MakeRange(list);
if (range.length() < 1) {
EnqueueError(LOCAL_GL_INVALID_VALUE, "Length of `list` must be >=1.");
float arr[1];
if (!MakeArrayFromList(list, arr)) {
return;
}
VertexAttrib1f(index, range[0]);
VertexAttrib1f(index, arr[0]);
}
void VertexAttrib2fv(const GLuint index, const Float32ListU& list) {
const FuncScope funcScope(*this, "vertexAttrib1fv");
if (IsContextLost()) return;
const auto range = MakeRange(list);
if (range.length() < 2) {
EnqueueError(LOCAL_GL_INVALID_VALUE, "Length of `list` must be >=2.");
float arr[2];
if (!MakeArrayFromList(list, arr)) {
return;
}
VertexAttrib2f(index, range[0], range[1]);
VertexAttrib2f(index, arr[0], arr[1]);
}
void VertexAttrib3fv(const GLuint index, const Float32ListU& list) {
const FuncScope funcScope(*this, "vertexAttrib1fv");
if (IsContextLost()) return;
const auto range = MakeRange(list);
if (range.length() < 3) {
EnqueueError(LOCAL_GL_INVALID_VALUE, "Length of `list` must be >=3.");
float arr[3];
if (!MakeArrayFromList(list, arr)) {
return;
}
VertexAttrib3f(index, range[0], range[1], range[2]);
VertexAttrib3f(index, arr[0], arr[1], arr[2]);
}
void VertexAttrib4fv(GLuint index, const Float32ListU& list) {
VertexAttrib4Tv(index, webgl::AttribBaseType::Float, MakeByteRange(list));
const FuncScope funcScope(*this, "vertexAttrib4fv");
float arr[4];
if (!MakeArrayFromList(list, arr)) {
return;
}
VertexAttrib4Tv(index, webgl::AttribBaseType::Float, MakeByteRange(arr));
}
void VertexAttribI4iv(GLuint index, const Int32ListU& list) {
VertexAttrib4Tv(index, webgl::AttribBaseType::Int, MakeByteRange(list));
const FuncScope funcScope(*this, "vertexAttribI4iv");
int32_t arr[4];
if (!MakeArrayFromList(list, arr)) {
return;
}
VertexAttrib4Tv(index, webgl::AttribBaseType::Int, MakeByteRange(arr));
}
void VertexAttribI4uiv(GLuint index, const Uint32ListU& list) {
VertexAttrib4Tv(index, webgl::AttribBaseType::Uint, MakeByteRange(list));
const FuncScope funcScope(*this, "vertexAttribI4uiv");
uint32_t arr[4];
if (!MakeArrayFromList(list, arr)) {
return;
}
VertexAttrib4Tv(index, webgl::AttribBaseType::Uint, MakeByteRange(arr));
}
void VertexAttribI4i(GLuint index, GLint x, GLint y, GLint z, GLint w) {
@ -2215,6 +2331,11 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
template <typename MethodType, MethodType method, typename... Args>
void Run(Args&&... aArgs) const;
// Same as above for use when using potentially GC-controlled data. The scope
// of `aNoGC` will be ended after the data is no longer needed.
template <typename MethodType, MethodType method, typename... Args>
void RunWithGCData(JS::AutoCheckCannotGC&& aNoGC, Args&&... aArgs) const;
// -------------------------------------------------------------------------
// Helpers for DOM operations, composition, actors, etc
// -------------------------------------------------------------------------

View File

@ -1337,7 +1337,6 @@ already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal(
"failed?)");
return nullptr;
}
array.ComputeState();
const SurfaceFormat FORMAT = SurfaceFormat::R8G8B8A8;
// ImageData's underlying data is not alpha-premultiplied.
auto alphaType = (aOptions.mPremultiplyAlpha == PremultiplyAlpha::Premultiply)
@ -1348,7 +1347,6 @@ already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal(
const uint32_t imageWidth = aImageData.Width();
const uint32_t imageHeight = aImageData.Height();
const uint32_t imageStride = imageWidth * BYTES_PER_PIXEL;
const uint32_t dataLength = array.Length();
const gfx::IntSize imageSize(imageWidth, imageHeight);
// Check the ImageData is neutered or not.
@ -1357,52 +1355,46 @@ already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal(
return nullptr;
}
if ((imageWidth * imageHeight * BYTES_PER_PIXEL) != dataLength) {
aRv.ThrowInvalidStateError("Data size / image format mismatch");
return nullptr;
}
return array.ProcessFixedData(
[&](const Span<const uint8_t>& aData) -> already_AddRefed<ImageBitmap> {
const uint32_t dataLength = aData.Length();
if ((imageWidth * imageHeight * BYTES_PER_PIXEL) != dataLength) {
aRv.ThrowInvalidStateError("Data size / image format mismatch");
return nullptr;
}
// Create and Crop the raw data into a layers::Image
RefPtr<layers::Image> data;
// Create and Crop the raw data into a layers::Image
RefPtr<layers::Image> data;
// If the data could move during a GC, copy it out into a local buffer that
// lives until a CreateImageFromRawData lower in the stack copies it.
// Reassure the static analysis that we know what we're doing.
size_t maxInline = JS_MaxMovableTypedArraySize();
uint8_t inlineDataBuffer[maxInline];
uint8_t* fixedData = array.FixedData(inlineDataBuffer, maxInline);
uint8_t* fixedData = const_cast<uint8_t*>(aData.Elements());
// Lie to the hazard analysis and say that we're done with everything that
// `array` was using (safe because the data buffer is fixed, and the holding
// JSObject is being kept alive elsewhere.)
array.Reset();
if (NS_IsMainThread()) {
data =
CreateImageFromRawData(imageSize, imageStride, FORMAT, fixedData,
dataLength, aCropRect, aOptions);
} else {
RefPtr<CreateImageFromRawDataInMainThreadSyncTask> task =
new CreateImageFromRawDataInMainThreadSyncTask(
fixedData, dataLength, imageStride, FORMAT, imageSize,
aCropRect, getter_AddRefs(data), aOptions);
task->Dispatch(Canceling, aRv);
}
if (NS_IsMainThread()) {
data = CreateImageFromRawData(imageSize, imageStride, FORMAT, fixedData,
dataLength, aCropRect, aOptions);
} else {
RefPtr<CreateImageFromRawDataInMainThreadSyncTask> task =
new CreateImageFromRawDataInMainThreadSyncTask(
fixedData, dataLength, imageStride, FORMAT, imageSize, aCropRect,
getter_AddRefs(data), aOptions);
task->Dispatch(Canceling, aRv);
}
if (NS_WARN_IF(!data)) {
aRv.ThrowInvalidStateError("Failed to create internal image");
return nullptr;
}
if (NS_WARN_IF(!data)) {
aRv.ThrowInvalidStateError("Failed to create internal image");
return nullptr;
}
// Create an ImageBitmap.
RefPtr<ImageBitmap> ret =
new ImageBitmap(aGlobal, data, false /* write-only */, alphaType);
ret->mAllocatedImageData = true;
// Create an ImageBitmap.
RefPtr<ImageBitmap> ret =
new ImageBitmap(aGlobal, data, false /* write-only */, alphaType);
// The cropping information has been handled in the
// CreateImageFromRawData() function.
ret->mAllocatedImageData = true;
// The cropping information has been handled in the CreateImageFromRawData()
// function.
return ret.forget();
return ret.forget();
});
}
/* static */

View File

@ -51,8 +51,7 @@ already_AddRefed<ImageData> ImageData::Constructor(const GlobalObject& aGlobal,
return nullptr;
}
// Restrict the typed array length to INT32_MAX because that's all we support
// in dom::TypedArray::ComputeState.
// Restrict the typed array length to INT32_MAX because that's all we support.
CheckedInt<uint32_t> length = CheckedInt<uint32_t>(aWidth) * aHeight * 4;
if (!length.isValid() || length.value() > INT32_MAX) {
aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
@ -73,9 +72,11 @@ already_AddRefed<ImageData> ImageData::Constructor(
const GlobalObject& aGlobal, const Uint8ClampedArray& aData,
const uint32_t aWidth, const Optional<uint32_t>& aHeight,
ErrorResult& aRv) {
aData.ComputeState();
uint32_t length = aData.Length();
Maybe<uint32_t> maybeLength = aData.ProcessData(
[&](const Span<uint8_t>& aData, JS::AutoCheckCannotGC&& nogc) {
return Some(aData.Length());
});
uint32_t length = maybeLength.valueOr(0);
if (length == 0 || length % 4) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;

View File

@ -1108,14 +1108,6 @@ inline Range<const T> MakeRange(const RawBuffer<T>& from) {
return from.Data();
}
// abv = ArrayBufferView
template <typename T>
inline auto MakeRangeAbv(const T& abv)
-> Range<const typename T::element_type> {
abv.ComputeState();
return {abv.Data(), abv.Length()};
}
// -
constexpr auto kUniversalAlignment = alignof(std::max_align_t);
@ -1134,10 +1126,6 @@ inline size_t ByteSize(const Range<T>& range) {
return range.length() * sizeof(T);
}
Maybe<Range<const uint8_t>> GetRangeFromView(const dom::ArrayBufferView& view,
GLuint elemOffset,
GLuint elemCountOverride);
// -
template <typename T>

View File

@ -45,7 +45,6 @@ struct RsaHashedKeyAlgorithmStorage {
aRsa.mModulusLength = mModulusLength;
aRsa.mHash.mName = mHash.mName;
aRsa.mPublicExponent.Init(exponent);
aRsa.mPublicExponent.ComputeState();
return true;
}

View File

@ -104,20 +104,11 @@ void TextDecoder::Decode(const Optional<ArrayBufferViewOrArrayBuffer>& aBuffer,
DecodeNative(nullptr, aOptions.mStream, aOutDecodedString, aRv);
return;
}
const ArrayBufferViewOrArrayBuffer& buf = aBuffer.Value();
uint8_t* data;
uint32_t length;
if (buf.IsArrayBufferView()) {
buf.GetAsArrayBufferView().ComputeState();
data = buf.GetAsArrayBufferView().Data();
length = buf.GetAsArrayBufferView().Length();
} else {
MOZ_ASSERT(buf.IsArrayBuffer());
buf.GetAsArrayBuffer().ComputeState();
data = buf.GetAsArrayBuffer().Data();
length = buf.GetAsArrayBuffer().Length();
}
DecodeNative(Span(data, length), aOptions.mStream, aOutDecodedString, aRv);
ProcessTypedArrays(aBuffer.Value(), [&](const Span<uint8_t>& aData,
JS::AutoCheckCannotGC&&) {
DecodeNative(aData, aOptions.mStream, aOutDecodedString, aRv);
});
}
void TextDecoderCommon::GetEncoding(nsAString& aEncoding) {

View File

@ -46,28 +46,6 @@ JSObject* TextDecoderStream::WrapObject(JSContext* aCx,
return TextDecoderStream_Binding::Wrap(aCx, this, aGivenProto);
}
// TODO: This does not allow shared array buffers, just as the non-stream
// TextDecoder/Encoder don't. (Bug 1561594)
Span<const uint8_t> ExtractSpanFromBufferSource(
JSContext* aCx, JS::Handle<JS::Value> aBufferSource, ErrorResult& aRv) {
RootedUnion<OwningArrayBufferViewOrArrayBuffer> bufferSource(aCx);
if (!bufferSource.Init(aCx, aBufferSource)) {
aRv.MightThrowJSException();
aRv.StealExceptionFromJSContext(aCx);
return Span<const uint8_t>();
}
if (bufferSource.IsArrayBufferView()) {
ArrayBufferView& view = bufferSource.GetAsArrayBufferView();
view.ComputeState();
return Span(view.Data(), view.Length());
}
ArrayBuffer& buffer = bufferSource.GetAsArrayBuffer();
buffer.ComputeState();
return Span(buffer.Data(), buffer.Length());
}
class TextDecoderStreamAlgorithms : public TransformerAlgorithmsWrapper {
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TextDecoderStreamAlgorithms,
@ -81,24 +59,22 @@ class TextDecoderStreamAlgorithms : public TransformerAlgorithmsWrapper {
// Note that the most of the decoding algorithm is implemented in
// mozilla::Decoder, and this is mainly about calling it properly.
// https://encoding.spec.whatwg.org/#decode-and-enqueue-a-chunk
MOZ_CAN_RUN_SCRIPT void DecodeSpanAndEnqueue(
JSContext* aCx, Span<const uint8_t> aInput, bool aFlush,
// TODO: This does not allow shared array buffers, just as the non-stream
// TextDecoder/Encoder don't. (Bug 1561594)
MOZ_CAN_RUN_SCRIPT void DecodeBufferSourceAndEnqueue(
JSContext* aCx, OwningArrayBufferViewOrArrayBuffer* aInput, bool aFlush,
TransformStreamDefaultController& aController, ErrorResult& aRv) {
CheckedInt<nsAString::size_type> needed =
mDecoderStream->Decoder()->MaxUTF16BufferLength(aInput.Length());
if (!needed.isValid()) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
nsString outDecodedString;
auto output = outDecodedString.GetMutableData(needed.value(), fallible);
if (!output) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
if (aInput) {
ProcessTypedArrays(*aInput, [&](const Span<const uint8_t>& aData,
JS::AutoCheckCannotGC&&) {
mDecoderStream->DecodeNative(aData, !aFlush, outDecodedString, aRv);
});
} else {
mDecoderStream->DecodeNative(Span<const uint8_t>(), !aFlush,
outDecodedString, aRv);
}
mDecoderStream->DecodeNative(aInput, !aFlush, outDecodedString, aRv);
if (aRv.Failed()) {
return;
}
@ -107,7 +83,7 @@ class TextDecoderStreamAlgorithms : public TransformerAlgorithmsWrapper {
// Step 4.2. If outputChunk is non-empty, then enqueue outputChunk in
// decoders transform.
JS::Rooted<JS::Value> outputChunk(aCx);
if (!xpc::NonVoidStringToJsval(aCx, outDecodedString, &outputChunk)) {
if (!ToJSValue(aCx, outDecodedString, &outputChunk)) {
JS_ClearPendingException(aCx);
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
@ -136,13 +112,14 @@ class TextDecoderStreamAlgorithms : public TransformerAlgorithmsWrapper {
// Step 1. Let bufferSource be the result of converting chunk to an
// [AllowShared] BufferSource.
// (But here we get a mozilla::Span instead)
Span<const uint8_t> input = ExtractSpanFromBufferSource(cx, aChunk, aRv);
if (aRv.Failed()) {
RootedUnion<OwningArrayBufferViewOrArrayBuffer> bufferSource(cx);
if (!bufferSource.Init(cx, aChunk)) {
aRv.MightThrowJSException();
aRv.StealExceptionFromJSContext(cx);
return;
}
DecodeSpanAndEnqueue(cx, input, false, aController, aRv);
DecodeBufferSourceAndEnqueue(cx, &bufferSource, false, aController, aRv);
}
// https://encoding.spec.whatwg.org/#dom-textdecoderstream
@ -162,7 +139,7 @@ class TextDecoderStreamAlgorithms : public TransformerAlgorithmsWrapper {
// https://encoding.spec.whatwg.org/#flush-and-enqueue
// (The flush and enqueue algorithm is basically a subset of decode and
// enqueue one, so let's reuse it)
DecodeSpanAndEnqueue(cx, Span<const uint8_t>(), true, aController, aRv);
DecodeBufferSourceAndEnqueue(cx, nullptr, true, aController, aRv);
}
private:

View File

@ -69,9 +69,6 @@ class TextDecoderStream final : public nsISupports,
RefPtr<TransformStream> mStream;
};
Span<const uint8_t> ExtractSpanFromBufferSource(
JSContext* aCx, JS::Handle<JS::Value> aBufferSource, ErrorResult& aRv);
} // namespace mozilla::dom
#endif // DOM_ENCODING_TEXTDECODERSTREAM_H_

View File

@ -30,17 +30,21 @@ void TextEncoder::EncodeInto(JSContext* aCx, JS::Handle<JSString*> aSrc,
const Uint8Array& aDst,
TextEncoderEncodeIntoResult& aResult,
OOMReporter& aError) {
aDst.ComputeState();
size_t read;
size_t written;
auto maybe = JS_EncodeStringToUTF8BufferPartial(
aCx, aSrc, AsWritableChars(Span(aDst.Data(), aDst.Length())));
DebugOnly<size_t> dstLength = 0;
auto maybe = aDst.ProcessData(
[&](const Span<uint8_t>& aData, JS::AutoCheckCannotGC&&) {
dstLength = aData.Length();
return JS_EncodeStringToUTF8BufferPartial(aCx, aSrc,
AsWritableChars(aData));
});
if (!maybe) {
aError.ReportOOM();
return;
}
size_t read;
size_t written;
std::tie(read, written) = *maybe;
MOZ_ASSERT(written <= aDst.Length());
MOZ_ASSERT(written <= dstLength);
aResult.mRead.Construct() = read;
aResult.mWritten.Construct() = written;
}

View File

@ -536,24 +536,18 @@ uint64_t FileSystemSyncAccessHandle::ReadOrWrite(
return 0;
};
const auto dataSpan = [&aBuffer]() {
if (aBuffer.IsArrayBuffer()) {
const ArrayBuffer& buffer = aBuffer.GetAsArrayBuffer();
buffer.ComputeState();
return Span{buffer.Data(), buffer.Length()};
// Handle seek before read ('at')
const auto at = [&aOptions]() -> uint64_t {
if (aOptions.mAt.WasPassed()) {
return aOptions.mAt.Value();
}
MOZ_ASSERT(aBuffer.IsArrayBufferView());
const ArrayBufferView& buffer = aBuffer.GetAsArrayBufferView();
buffer.ComputeState();
return Span{buffer.Data(), buffer.Length()};
// Spec says default for at is 0 (2.6)
return 0;
}();
CheckedInt<int64_t> offset = 0;
if (aOptions.mAt.WasPassed()) {
// Handle seek before read ('at')
offset = CheckedInt<int64_t>(aOptions.mAt.Value());
QM_TRY(MOZ_TO_RESULT(offset.isValid()), throwAndReturn);
}
const auto offset = CheckedInt<int64_t>(at);
QM_TRY(MOZ_TO_RESULT(offset.isValid()), throwAndReturn);
AutoSyncLoopHolder syncLoop(mWorkerRef->Private(), Canceling);
nsCOMPtr<nsISerialEventTarget> syncLoopTarget =
@ -565,72 +559,73 @@ uint64_t FileSystemSyncAccessHandle::ReadOrWrite(
uint64_t totalCount = 0;
InvokeAsync(
mIOTaskQueue, __func__,
[selfHolder = fs::TargetPtrHolder(this), dataSpan,
use_offset = aOptions.mAt.WasPassed(), offset, aRead, &totalCount]() {
QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()),
CreateAndRejectBoolPromise);
if (use_offset) {
LOG_VERBOSE(("%p: Seeking to %" PRIu64, selfHolder->mStream.get(),
offset.value()));
QM_TRY(MOZ_TO_RESULT(selfHolder->mStream->Seek(
nsISeekableStream::NS_SEEK_SET, offset.value())),
ProcessTypedArraysFixed(aBuffer, [&](const Span<uint8_t> aData) {
InvokeAsync(
mIOTaskQueue, __func__,
[selfHolder = fs::TargetPtrHolder(this), aData,
use_offset = aOptions.mAt.WasPassed(), offset, aRead, syncLoopTarget,
&totalCount]() {
QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()),
CreateAndRejectBoolPromise);
}
if (use_offset) {
LOG_VERBOSE(("%p: Seeking to %" PRIu64, selfHolder->mStream.get(),
offset.value()));
nsCOMPtr<nsIInputStream> inputStream;
nsCOMPtr<nsIOutputStream> outputStream;
QM_TRY(MOZ_TO_RESULT(selfHolder->mStream->Seek(
nsISeekableStream::NS_SEEK_SET, offset.value())),
CreateAndRejectBoolPromise);
}
if (aRead) {
LOG_VERBOSE(("%p: Reading %zu bytes", selfHolder->mStream.get(),
dataSpan.Length()));
nsCOMPtr<nsIInputStream> inputStream;
nsCOMPtr<nsIOutputStream> outputStream;
inputStream = selfHolder->mStream->InputStream();
if (aRead) {
LOG_VERBOSE(("%p: Reading %zu bytes", selfHolder->mStream.get(),
aData.Length()));
outputStream =
FixedBufferOutputStream::Create(AsWritableChars(dataSpan));
} else {
LOG_VERBOSE(("%p: Writing %zu bytes", selfHolder->mStream.get(),
dataSpan.Length()));
inputStream = selfHolder->mStream->InputStream();
outputStream =
FixedBufferOutputStream::Create(AsWritableChars(aData));
} else {
LOG_VERBOSE(("%p: Writing %zu bytes", selfHolder->mStream.get(),
aData.Length()));
QM_TRY(MOZ_TO_RESULT(NS_NewByteInputStream(
getter_AddRefs(inputStream), AsChars(dataSpan),
NS_ASSIGNMENT_DEPEND)),
QM_TRY(MOZ_TO_RESULT(NS_NewByteInputStream(
getter_AddRefs(inputStream), AsChars(aData),
NS_ASSIGNMENT_DEPEND)),
CreateAndRejectBoolPromise);
outputStream = selfHolder->mStream->OutputStream();
}
auto promiseHolder = MakeUnique<MozPromiseHolder<BoolPromise>>();
RefPtr<BoolPromise> promise = promiseHolder->Ensure(__func__);
QM_TRY(MOZ_TO_RESULT(fs::AsyncCopy(
inputStream, outputStream, GetCurrentSerialEventTarget(),
aRead ? NS_ASYNCCOPY_VIA_WRITESEGMENTS
: NS_ASYNCCOPY_VIA_READSEGMENTS,
/* aCloseSource */ !aRead, /* aCloseSink */ aRead,
[&totalCount](uint32_t count) { totalCount += count; },
[promiseHolder = std::move(promiseHolder)](nsresult rv) {
promiseHolder->ResolveIfExists(true, __func__);
})),
CreateAndRejectBoolPromise);
outputStream = selfHolder->mStream->OutputStream();
}
return promise;
})
->Then(syncLoopTarget, __func__,
[this, &syncLoopTarget](
const BoolPromise::ResolveOrRejectValue& aValue) {
MOZ_ASSERT(mWorkerRef);
auto promiseHolder = MakeUnique<MozPromiseHolder<BoolPromise>>();
RefPtr<BoolPromise> promise = promiseHolder->Ensure(__func__);
mWorkerRef->Private()->AssertIsOnWorkerThread();
QM_TRY(MOZ_TO_RESULT(fs::AsyncCopy(
inputStream, outputStream, GetCurrentSerialEventTarget(),
aRead ? NS_ASYNCCOPY_VIA_WRITESEGMENTS
: NS_ASYNCCOPY_VIA_READSEGMENTS,
/* aCloseSource */ !aRead, /* aCloseSink */ aRead,
[&totalCount](uint32_t count) { totalCount += count; },
[promiseHolder = std::move(promiseHolder)](nsresult rv) {
promiseHolder->ResolveIfExists(true, __func__);
})),
CreateAndRejectBoolPromise);
mWorkerRef->Private()->StopSyncLoop(syncLoopTarget, NS_OK);
});
return promise;
})
->Then(syncLoopTarget, __func__,
[this, &syncLoopTarget](
const BoolPromise::ResolveOrRejectValue& aValue) {
MOZ_ASSERT(mWorkerRef);
mWorkerRef->Private()->AssertIsOnWorkerThread();
mWorkerRef->Private()->StopSyncLoop(syncLoopTarget, NS_OK);
});
MOZ_ALWAYS_SUCCEEDS(syncLoop.Run());
MOZ_ALWAYS_SUCCEEDS(syncLoop.Run());
});
return totalCount;
}

View File

@ -23,24 +23,6 @@ LogModule* GetEMEVerboseLog() {
return log;
}
ArrayData GetArrayBufferViewOrArrayBufferData(
const dom::ArrayBufferViewOrArrayBuffer& aBufferOrView) {
MOZ_ASSERT(aBufferOrView.IsArrayBuffer() ||
aBufferOrView.IsArrayBufferView());
JS::AutoCheckCannotGC nogc;
if (aBufferOrView.IsArrayBuffer()) {
const dom::ArrayBuffer& buffer = aBufferOrView.GetAsArrayBuffer();
buffer.ComputeState();
return ArrayData(buffer.Data(), buffer.Length());
} else if (aBufferOrView.IsArrayBufferView()) {
const dom::ArrayBufferView& bufferview =
aBufferOrView.GetAsArrayBufferView();
bufferview.ComputeState();
return ArrayData(bufferview.Data(), bufferview.Length());
}
return ArrayData(nullptr, 0);
}
void CopyArrayBufferViewOrArrayBufferData(
const dom::ArrayBufferViewOrArrayBuffer& aBufferOrView,
nsTArray<uint8_t>& aOutData) {

View File

@ -47,36 +47,6 @@ void CopyArrayBufferViewOrArrayBufferData(
const dom::ArrayBufferViewOrArrayBuffer& aBufferOrView,
nsTArray<uint8_t>& aOutData);
struct ArrayData {
explicit ArrayData(const uint8_t* aData, size_t aLength)
: mData(aData), mLength(aLength) {}
const uint8_t* mData;
const size_t mLength;
bool IsValid() const { return mData != nullptr && mLength != 0; }
bool operator==(const nsTArray<uint8_t>& aOther) const {
return mLength == aOther.Length() &&
memcmp(mData, aOther.Elements(), mLength) == 0;
}
};
// Helper function to extract data coming in from JS in an
// (ArrayBuffer or ArrayBufferView) IDL typed function argument.
//
// Be *very* careful with this!
//
// Only use returned ArrayData inside the lifetime of the
// ArrayBufferViewOrArrayBuffer; the ArrayData struct does not contain
// a copy of the data!
//
// And do *not* call out to anything that could call into JavaScript,
// while the ArrayData is live, as then all bets about the data not changing
// are off! No calls into JS, no calls into JS-implemented WebIDL or XPIDL,
// nothing. Beware!
//
// Only call this on a properly initialized ArrayBufferViewOrArrayBuffer.
ArrayData GetArrayBufferViewOrArrayBufferData(
const dom::ArrayBufferViewOrArrayBuffer& aBufferOrView);
nsString KeySystemToProxyName(const nsAString& aKeySystem);
bool IsClearkeyKeySystem(const nsAString& aKeySystem);

View File

@ -35,36 +35,36 @@ nsPIDOMWindowInner* MediaKeyStatusMap::GetParentObject() const {
return mParent;
}
const MediaKeyStatusMap::KeyStatus* MediaKeyStatusMap::FindKey(
const ArrayBufferViewOrArrayBuffer& aKey) const {
MOZ_ASSERT(aKey.IsArrayBuffer() || aKey.IsArrayBufferView());
return ProcessTypedArrays(aKey,
[&](const Span<const uint8_t>& aData,
JS::AutoCheckCannotGC&&) -> const KeyStatus* {
for (const KeyStatus& status : mStatuses) {
if (aData == Span(status.mKeyId)) {
return &status;
}
}
return nullptr;
});
}
void MediaKeyStatusMap::Get(const ArrayBufferViewOrArrayBuffer& aKey,
OwningMediaKeyStatusOrUndefined& aOutValue,
ErrorResult& aOutRv) const {
ArrayData keyId = GetArrayBufferViewOrArrayBufferData(aKey);
if (!keyId.IsValid()) {
const KeyStatus* status = FindKey(aKey);
if (!status) {
aOutValue.SetUndefined();
return;
}
for (const KeyStatus& status : mStatuses) {
if (keyId == status.mKeyId) {
aOutValue.SetAsMediaKeyStatus() = status.mStatus;
return;
}
}
aOutValue.SetUndefined();
aOutValue.SetAsMediaKeyStatus() = status->mStatus;
}
bool MediaKeyStatusMap::Has(const ArrayBufferViewOrArrayBuffer& aKey) const {
ArrayData keyId = GetArrayBufferViewOrArrayBufferData(aKey);
if (!keyId.IsValid()) {
return false;
}
for (const KeyStatus& status : mStatuses) {
if (keyId == status.mKeyId) {
return true;
}
}
return false;
return FindKey(aKey);
}
uint32_t MediaKeyStatusMap::GetIterableLength() const {

View File

@ -83,6 +83,8 @@ class MediaKeyStatusMap final : public nsISupports, public nsWrapperCache {
MediaKeyStatus mStatus;
};
const KeyStatus* FindKey(const ArrayBufferViewOrArrayBuffer& aKey) const;
nsTArray<KeyStatus> mStatuses;
};

View File

@ -15,6 +15,7 @@
#include "mozilla/Preferences.h"
#include "mozilla/dom/MediaSourceBinding.h"
#include "mozilla/dom/TimeRanges.h"
#include "mozilla/dom/TypedArray.h"
#include "nsError.h"
#include "nsIRunnable.h"
#include "nsThreadUtils.h"
@ -178,18 +179,24 @@ void SourceBuffer::SetAppendWindowEnd(double aAppendWindowEnd,
void SourceBuffer::AppendBuffer(const ArrayBuffer& aData, ErrorResult& aRv) {
MOZ_ASSERT(NS_IsMainThread());
MSE_API("AppendBuffer(ArrayBuffer)");
aData.ComputeState();
DDLOG(DDLogCategory::API, "AppendBuffer", aData.Length());
AppendData(aData.Data(), aData.Length(), aRv);
RefPtr<MediaByteBuffer> data = PrepareAppend(aData, aRv);
if (!data) {
return;
}
DDLOG(DDLogCategory::API, "AppendBuffer", uint64_t(data->Length()));
AppendData(std::move(data), aRv);
}
void SourceBuffer::AppendBuffer(const ArrayBufferView& aData,
ErrorResult& aRv) {
MOZ_ASSERT(NS_IsMainThread());
MSE_API("AppendBuffer(ArrayBufferView)");
aData.ComputeState();
DDLOG(DDLogCategory::API, "AppendBuffer", aData.Length());
AppendData(aData.Data(), aData.Length(), aRv);
RefPtr<MediaByteBuffer> data = PrepareAppend(aData, aRv);
if (!data) {
return;
}
DDLOG(DDLogCategory::API, "AppendBuffer", uint64_t(data->Length()));
AppendData(std::move(data), aRv);
}
already_AddRefed<Promise> SourceBuffer::AppendBufferAsync(
@ -197,10 +204,13 @@ already_AddRefed<Promise> SourceBuffer::AppendBufferAsync(
MOZ_ASSERT(NS_IsMainThread());
MSE_API("AppendBufferAsync(ArrayBuffer)");
aData.ComputeState();
DDLOG(DDLogCategory::API, "AppendBufferAsync", aData.Length());
RefPtr<MediaByteBuffer> data = PrepareAppend(aData, aRv);
if (!data) {
return nullptr;
}
DDLOG(DDLogCategory::API, "AppendBufferAsync", uint64_t(data->Length()));
return AppendDataAsync(aData.Data(), aData.Length(), aRv);
return AppendDataAsync(std::move(data), aRv);
}
already_AddRefed<Promise> SourceBuffer::AppendBufferAsync(
@ -208,10 +218,13 @@ already_AddRefed<Promise> SourceBuffer::AppendBufferAsync(
MOZ_ASSERT(NS_IsMainThread());
MSE_API("AppendBufferAsync(ArrayBufferView)");
aData.ComputeState();
DDLOG(DDLogCategory::API, "AppendBufferAsync", aData.Length());
RefPtr<MediaByteBuffer> data = PrepareAppend(aData, aRv);
if (!data) {
return nullptr;
}
DDLOG(DDLogCategory::API, "AppendBufferAsync", uint64_t(data->Length()));
return AppendDataAsync(aData.Data(), aData.Length(), aRv);
return AppendDataAsync(std::move(data), aRv);
}
void SourceBuffer::Abort(ErrorResult& aRv) {
@ -546,27 +559,22 @@ void SourceBuffer::CheckEndTime() {
}
}
void SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength,
void SourceBuffer::AppendData(RefPtr<MediaByteBuffer>&& aData,
ErrorResult& aRv) {
MOZ_ASSERT(NS_IsMainThread());
MSE_DEBUG("AppendData(aLength=%u)", aLength);
MSE_DEBUG("AppendData(aLength=%zu)", aData->Length());
RefPtr<MediaByteBuffer> data = PrepareAppend(aData, aLength, aRv);
if (!data) {
return;
}
StartUpdating();
mTrackBuffersManager->AppendData(data.forget(), mCurrentAttributes)
mTrackBuffersManager->AppendData(aData.forget(), mCurrentAttributes)
->Then(mAbstractMainThread, __func__, this,
&SourceBuffer::AppendDataCompletedWithSuccess,
&SourceBuffer::AppendDataErrored)
->Track(mPendingAppend);
}
already_AddRefed<Promise> SourceBuffer::AppendDataAsync(const uint8_t* aData,
uint32_t aLength,
ErrorResult& aRv) {
already_AddRefed<Promise> SourceBuffer::AppendDataAsync(
RefPtr<MediaByteBuffer>&& aData, ErrorResult& aRv) {
MOZ_ASSERT(NS_IsMainThread());
if (!IsAttached()) {
@ -586,7 +594,7 @@ already_AddRefed<Promise> SourceBuffer::AppendDataAsync(const uint8_t* aData,
return nullptr;
}
AppendData(aData, aLength, aRv);
AppendData(std::move(aData), aRv);
if (aRv.Failed()) {
return nullptr;
@ -718,6 +726,13 @@ already_AddRefed<MediaByteBuffer> SourceBuffer::PrepareAppend(
return data.forget();
}
template <typename T>
already_AddRefed<MediaByteBuffer> SourceBuffer::PrepareAppend(
const T& aData, ErrorResult& aRv) {
return aData.ProcessFixedData([&](const Span<uint8_t>& aData) {
return PrepareAppend(aData.Elements(), aData.Length(), aRv);
});
}
TimeUnit SourceBuffer::GetBufferedEnd() {
MOZ_ASSERT(NS_IsMainThread());
ErrorResult dummy;

View File

@ -151,10 +151,10 @@ class SourceBuffer final : public DOMEventTargetHelper,
void CheckEndTime();
// Shared implementation of AppendBuffer overloads.
void AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv);
void AppendData(RefPtr<MediaByteBuffer>&& aData, ErrorResult& aRv);
// Shared implementation of AppendBufferAsync overloads.
already_AddRefed<Promise> AppendDataAsync(const uint8_t* aData,
uint32_t aLength, ErrorResult& aRv);
already_AddRefed<Promise> AppendDataAsync(RefPtr<MediaByteBuffer>&& aData,
ErrorResult& aRv);
void PrepareRemove(double aStart, double aEnd, ErrorResult& aRv);
@ -169,6 +169,9 @@ class SourceBuffer final : public DOMEventTargetHelper,
already_AddRefed<MediaByteBuffer> PrepareAppend(const uint8_t* aData,
uint32_t aLength,
ErrorResult& aRv);
template <typename T>
already_AddRefed<MediaByteBuffer> PrepareAppend(const T& aData,
ErrorResult& aRv);
void AppendDataCompletedWithSuccess(
const SourceBufferTask::AppendBufferResult& aResult);

View File

@ -215,15 +215,14 @@ void AnalyserNode::GetFloatFrequencyData(const Float32Array& aArray) {
return;
}
aArray.ComputeState();
aArray.ProcessData([&](const Span<float>& aData, JS::AutoCheckCannotGC&&) {
size_t length = std::min(size_t(aData.Length()), mOutputBuffer.Length());
float* buffer = aArray.Data();
size_t length = std::min(size_t(aArray.Length()), mOutputBuffer.Length());
for (size_t i = 0; i < length; ++i) {
buffer[i] = WebAudioUtils::ConvertLinearToDecibels(
mOutputBuffer[i], -std::numeric_limits<float>::infinity());
}
for (size_t i = 0; i < length; ++i) {
aData[i] = WebAudioUtils::ConvertLinearToDecibels(
mOutputBuffer[i], -std::numeric_limits<float>::infinity());
}
});
}
void AnalyserNode::GetByteFrequencyData(const Uint8Array& aArray) {
@ -234,51 +233,50 @@ void AnalyserNode::GetByteFrequencyData(const Uint8Array& aArray) {
const double rangeScaleFactor = 1.0 / (mMaxDecibels - mMinDecibels);
aArray.ComputeState();
aArray.ProcessData([&](const Span<uint8_t>& aData, JS::AutoCheckCannotGC&&) {
size_t length = std::min(size_t(aData.Length()), mOutputBuffer.Length());
unsigned char* buffer = aArray.Data();
size_t length = std::min(size_t(aArray.Length()), mOutputBuffer.Length());
for (size_t i = 0; i < length; ++i) {
const double decibels =
WebAudioUtils::ConvertLinearToDecibels(mOutputBuffer[i], mMinDecibels);
// scale down the value to the range of [0, UCHAR_MAX]
const double scaled = std::max(
0.0, std::min(double(UCHAR_MAX), UCHAR_MAX * (decibels - mMinDecibels) *
rangeScaleFactor));
buffer[i] = static_cast<unsigned char>(scaled);
}
for (size_t i = 0; i < length; ++i) {
const double decibels = WebAudioUtils::ConvertLinearToDecibels(
mOutputBuffer[i], mMinDecibels);
// scale down the value to the range of [0, UCHAR_MAX]
const double scaled = std::max(
0.0,
std::min(double(UCHAR_MAX),
UCHAR_MAX * (decibels - mMinDecibels) * rangeScaleFactor));
aData[i] = static_cast<unsigned char>(scaled);
}
});
}
void AnalyserNode::GetFloatTimeDomainData(const Float32Array& aArray) {
aArray.ComputeState();
aArray.ProcessData([&](const Span<float>& aData, JS::AutoCheckCannotGC&&) {
size_t length = std::min(aData.Length(), size_t(FftSize()));
float* buffer = aArray.Data();
size_t length = std::min(aArray.Length(), FftSize());
GetTimeDomainData(buffer, length);
GetTimeDomainData(aData.Elements(), length);
});
}
void AnalyserNode::GetByteTimeDomainData(const Uint8Array& aArray) {
aArray.ComputeState();
aArray.ProcessData([&](const Span<uint8_t>& aData, JS::AutoCheckCannotGC&&) {
size_t length = std::min(aData.Length(), size_t(FftSize()));
size_t length = std::min(aArray.Length(), FftSize());
AlignedTArray<float> tmpBuffer;
if (!tmpBuffer.SetLength(length, fallible)) {
return;
}
AlignedTArray<float> tmpBuffer;
if (!tmpBuffer.SetLength(length, fallible)) {
return;
}
GetTimeDomainData(tmpBuffer.Elements(), length);
GetTimeDomainData(tmpBuffer.Elements(), length);
unsigned char* buffer = aArray.Data();
for (size_t i = 0; i < length; ++i) {
const float value = tmpBuffer[i];
// scale the value to the range of [0, UCHAR_MAX]
const float scaled =
std::max(0.0f, std::min(float(UCHAR_MAX), 128.0f * (value + 1.0f)));
buffer[i] = static_cast<unsigned char>(scaled);
}
unsigned char* buffer = aData.Elements();
for (size_t i = 0; i < length; ++i) {
const float value = tmpBuffer[i];
// scale the value to the range of [0, UCHAR_MAX]
const float scaled =
std::max(0.0f, std::min(float(UCHAR_MAX), 128.0f * (value + 1.0f)));
buffer[i] = static_cast<unsigned char>(scaled);
}
});
}
bool AnalyserNode::FFTAnalysis() {

View File

@ -323,8 +323,10 @@ void AudioBuffer::CopyFromChannel(const Float32Array& aDestination,
return;
}
JS::AutoCheckCannotGC nogc;
aDestination.ComputeState();
uint32_t count = std::min(length - aBufferOffset, aDestination.Length());
MOZ_RELEASE_ASSERT(!JS_GetTypedArraySharedness(aDestination.Obj()));
auto calculateCount = [=](uint32_t aLength) -> uint32_t {
return std::min(length - aBufferOffset, aLength);
};
JSObject* channelArray = mJSChannels[aChannelNumber];
if (channelArray) {
@ -338,17 +340,27 @@ void AudioBuffer::CopyFromChannel(const Float32Array& aDestination,
// The sourceData arrays should all have originated in
// RestoreJSChannelData, where they are created unshared.
MOZ_ASSERT(!isShared);
PodMove(aDestination.Data(), sourceData + aBufferOffset, count);
aDestination.ProcessData(
[&](const Span<float>& aData, JS::AutoCheckCannotGC&&) {
PodMove(aData.Elements(), sourceData + aBufferOffset,
calculateCount(aData.Length()));
});
return;
}
if (!mSharedChannels.IsNull()) {
CopyChannelDataToFloat(mSharedChannels, aChannelNumber, aBufferOffset,
aDestination.Data(), count);
aDestination.ProcessData([&](const Span<float>& aData,
JS::AutoCheckCannotGC&&) {
CopyChannelDataToFloat(mSharedChannels, aChannelNumber, aBufferOffset,
aData.Elements(), calculateCount(aData.Length()));
});
return;
}
PodZero(aDestination.Data(), count);
aDestination.ProcessData(
[&](const Span<float>& aData, JS::AutoCheckCannotGC&&) {
PodZero(aData.Elements(), calculateCount(aData.Length()));
});
}
void AudioBuffer::CopyToChannel(JSContext* aJSContext,
@ -374,14 +386,20 @@ void AudioBuffer::CopyToChannel(JSContext* aJSContext,
return;
}
aSource.ComputeState();
uint32_t count = std::min(length - aBufferOffset, aSource.Length());
bool isShared = false;
float* channelData = JS_GetFloat32ArrayData(channelArray, &isShared, nogc);
// The channelData arrays should all have originated in
// RestoreJSChannelData, where they are created unshared.
MOZ_ASSERT(!isShared);
PodMove(channelData + aBufferOffset, aSource.Data(), count);
int64_t offset = aBufferOffset;
aSource.ProcessData([&](const Span<float>& aData, JS::AutoCheckCannotGC&&) {
MOZ_ASSERT_IF(std::numeric_limits<decltype(aData.Length())>::max() >
std::numeric_limits<int64_t>::max(),
aData.Length() <= std::numeric_limits<int64_t>::max());
int64_t srcLength = int64_t(aData.Length());
size_t count = std::max(int64_t(0), std::min(length - offset, srcLength));
bool isShared = false;
float* channelData = JS_GetFloat32ArrayData(channelArray, &isShared, nogc);
// The channelData arrays should all have originated in
// RestoreJSChannelData, where they are created unshared.
MOZ_ASSERT(!isShared);
PodMove(channelData + aBufferOffset, aData.Elements(), count);
});
}
void AudioBuffer::GetChannelData(JSContext* aJSContext, uint32_t aChannel,

View File

@ -659,19 +659,18 @@ already_AddRefed<Promise> AudioContext::DecodeAudioData(
}
JSAutoRealm ar(cx, obj);
aBuffer.ComputeState();
if (!aBuffer.Data()) {
// Detach the array buffer
size_t length = JS::GetArrayBufferByteLength(obj);
uint8_t* data = static_cast<uint8_t*>(JS::StealArrayBufferContents(cx, obj));
if (!data) {
JS_ClearPendingException(cx);
// Throw if the buffer is detached
aRv.ThrowTypeError("Buffer argument can't be a detached buffer");
return nullptr;
}
// Detach the array buffer
size_t length = aBuffer.Length();
uint8_t* data = static_cast<uint8_t*>(JS::StealArrayBufferContents(cx, obj));
// Sniff the content of the media.
// Failed type sniffing will be handled by AsyncDecodeWebAudio.
nsAutoCString contentType;

View File

@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "BiquadFilterNode.h"
#include <algorithm>
#include "AlignmentUtils.h"
#include "AudioNodeEngine.h"
#include "AudioNodeTrack.h"
@ -304,46 +305,54 @@ void BiquadFilterNode::GetFrequencyResponse(const Float32Array& aFrequencyHz,
const Float32Array& aMagResponse,
const Float32Array& aPhaseResponse,
ErrorResult& aRv) {
aFrequencyHz.ComputeState();
aMagResponse.ComputeState();
aPhaseResponse.ComputeState();
if (!(aFrequencyHz.Length() == aMagResponse.Length() &&
aMagResponse.Length() == aPhaseResponse.Length())) {
aRv.ThrowInvalidAccessError("Parameter lengths must match");
return;
}
uint32_t length = aFrequencyHz.Length();
if (!length) {
return;
}
auto frequencies = MakeUnique<float[]>(length);
float* frequencyHz = aFrequencyHz.Data();
const double nyquist = Context()->SampleRate() * 0.5;
// Normalize the frequencies
for (uint32_t i = 0; i < length; ++i) {
if (frequencyHz[i] >= 0 && frequencyHz[i] <= nyquist) {
frequencies[i] = static_cast<float>(frequencyHz[i] / nyquist);
} else {
frequencies[i] = std::numeric_limits<float>::quiet_NaN();
}
}
UniquePtr<float[]> frequencies;
size_t length;
const double currentTime = Context()->CurrentTime();
aFrequencyHz.ProcessData([&](const Span<float>& aFrequencyData,
JS::AutoCheckCannotGC&&) {
aMagResponse.ProcessData([&](const Span<float>& aMagData,
JS::AutoCheckCannotGC&&) {
aPhaseResponse.ProcessData([&](const Span<float>& aPhaseData,
JS::AutoCheckCannotGC&&) {
length = aFrequencyData.Length();
if (length != aMagData.Length() || length != aPhaseData.Length()) {
aRv.ThrowInvalidAccessError("Parameter lengths must match");
return;
}
double freq = mFrequency->GetValueAtTime(currentTime);
double q = mQ->GetValueAtTime(currentTime);
double gain = mGain->GetValueAtTime(currentTime);
double detune = mDetune->GetValueAtTime(currentTime);
if (length == 0) {
return;
}
WebCore::Biquad biquad;
SetParamsOnBiquad(biquad, Context()->SampleRate(), mType, freq, q, gain,
detune);
biquad.getFrequencyResponse(int(length), frequencies.get(),
aMagResponse.Data(), aPhaseResponse.Data());
frequencies = MakeUniqueForOverwriteFallible<float[]>(length);
if (!frequencies) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
const double nyquist = Context()->SampleRate() * 0.5;
std::transform(aFrequencyData.begin(), aFrequencyData.end(),
frequencies.get(), [&](float aFrequency) {
if (aFrequency >= 0 && aFrequency <= nyquist) {
return static_cast<float>(aFrequency / nyquist);
}
return std::numeric_limits<float>::quiet_NaN();
});
double freq = mFrequency->GetValueAtTime(currentTime);
double q = mQ->GetValueAtTime(currentTime);
double gain = mGain->GetValueAtTime(currentTime);
double detune = mDetune->GetValueAtTime(currentTime);
WebCore::Biquad biquad;
SetParamsOnBiquad(biquad, Context()->SampleRate(), mType, freq, q, gain,
detune);
biquad.getFrequencyResponse(int(length), frequencies.get(),
aMagData.Elements(), aPhaseData.Elements());
});
});
});
}
} // namespace mozilla::dom

View File

@ -226,33 +226,41 @@ JSObject* IIRFilterNode::WrapObject(JSContext* aCx,
void IIRFilterNode::GetFrequencyResponse(const Float32Array& aFrequencyHz,
const Float32Array& aMagResponse,
const Float32Array& aPhaseResponse) {
aFrequencyHz.ComputeState();
aMagResponse.ComputeState();
aPhaseResponse.ComputeState();
aFrequencyHz.ProcessData([&](const Span<float>& aFrequencyData,
JS::AutoCheckCannotGC&&) {
aMagResponse.ProcessData([&](const Span<float>& aMagData,
JS::AutoCheckCannotGC&&) {
aPhaseResponse.ProcessData([&](const Span<float>& aPhaseData,
JS::AutoCheckCannotGC&&) {
uint32_t length = std::min(
{aFrequencyData.Length(), aMagData.Length(), aPhaseData.Length()});
if (!length) {
return;
}
uint32_t length =
std::min(std::min(aFrequencyHz.Length(), aMagResponse.Length()),
aPhaseResponse.Length());
if (!length) {
return;
}
auto frequencies = MakeUniqueForOverwriteFallible<float[]>(length);
if (!frequencies) {
return;
}
auto frequencies = MakeUnique<float[]>(length);
float* frequencyHz = aFrequencyHz.Data();
const double nyquist = Context()->SampleRate() * 0.5;
const double nyquist = Context()->SampleRate() * 0.5;
// Normalize the frequencies
for (uint32_t i = 0; i < length; ++i) {
if (frequencyHz[i] >= 0 && frequencyHz[i] <= nyquist) {
frequencies[i] = static_cast<float>(frequencyHz[i] / nyquist);
} else {
frequencies[i] = std::numeric_limits<float>::quiet_NaN();
}
}
// Normalize the frequencies
std::transform(aFrequencyData.begin(), aFrequencyData.begin() + length,
frequencies.get(), [&](float aFrequency) {
if (aFrequency >= 0 && aFrequency <= nyquist) {
return static_cast<float>(aFrequency / nyquist);
}
blink::IIRFilter filter(&mFeedforward, &mFeedback);
filter.getFrequencyResponse(int(length), frequencies.get(),
aMagResponse.Data(), aPhaseResponse.Data());
return std::numeric_limits<float>::quiet_NaN();
});
blink::IIRFilter filter(&mFeedforward, &mFeedback);
filter.getFrequencyResponse(int(length), frequencies.get(),
aMagData.Elements(), aPhaseData.Elements());
});
});
});
}
} // namespace mozilla::dom

View File

@ -144,32 +144,30 @@ already_AddRefed<EncodedVideoChunk> EncodedVideoChunk::Constructor(
return nullptr;
}
auto r = GetSharedArrayBufferData(aInit.mData);
if (r.isErr()) {
aRv.Throw(r.unwrapErr());
return nullptr;
}
Span<uint8_t> buf = r.unwrap();
auto buffer = ProcessTypedArrays(
aInit.mData,
[&](const Span<uint8_t>& aData,
JS::AutoCheckCannotGC&&) -> RefPtr<MediaAlignedByteBuffer> {
// Make sure it's in uint32_t's range.
CheckedUint32 byteLength(aData.Length());
if (!byteLength.isValid()) {
aRv.Throw(NS_ERROR_INVALID_ARG);
return nullptr;
}
if (aData.Length() == 0) {
LOGW("Buffer for constructing EncodedVideoChunk is empty!");
}
RefPtr<MediaAlignedByteBuffer> buf = MakeRefPtr<MediaAlignedByteBuffer>(
aData.Elements(), aData.Length());
// Make sure it's in uint32_t's range.
CheckedUint32 byteLength(buf.size_bytes());
if (!byteLength.isValid()) {
aRv.Throw(NS_ERROR_INVALID_ARG);
return nullptr;
}
if (buf.size_bytes() == 0) {
LOGW("Buffer for constructing EncodedVideoChunk is empty!");
}
auto buffer =
MakeRefPtr<MediaAlignedByteBuffer>(buf.data(), buf.size_bytes());
// Instead of checking *buffer, size comparision is used to allow constructing
// a zero-sized EncodedVideoChunk.
if (!buffer || buffer->Size() != buf.size_bytes()) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return nullptr;
}
// Instead of checking *buf, size comparision is used to allow
// constructing a zero-sized EncodedVideoChunk.
if (!buf || buf->Size() != aData.Length()) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return nullptr;
}
return buf;
});
RefPtr<EncodedVideoChunk> chunk(new EncodedVideoChunk(
global, buffer.forget(), aInit.mType, aInit.mTimestamp,
@ -207,20 +205,15 @@ void EncodedVideoChunk::CopyTo(
ErrorResult& aRv) {
AssertIsOnOwningThread();
auto r = GetSharedArrayBufferData(aDestination);
if (r.isErr()) {
aRv.Throw(r.unwrapErr());
return;
}
Span<uint8_t> buf = r.unwrap();
ProcessTypedArraysFixed(aDestination, [&](const Span<uint8_t>& aData) {
if (mBuffer->Size() > aData.size_bytes()) {
aRv.ThrowTypeError(
"Destination ArrayBuffer smaller than source EncodedVideoChunk");
return;
}
if (mBuffer->Size() > buf.size_bytes()) {
aRv.ThrowTypeError(
"Destination ArrayBuffer smaller than source EncodedVideoChunk");
return;
}
PodCopy(buf.data(), mBuffer->Data(), mBuffer->Size());
PodCopy(aData.data(), mBuffer->Data(), mBuffer->Size());
});
}
// https://w3c.github.io/webcodecs/#ref-for-deserialization-steps%E2%91%A0

View File

@ -906,39 +906,40 @@ static Result<RefPtr<VideoFrame>, nsCString> CreateVideoFrameFromBuffer(
MOZ_TRY_VAR(combinedLayout,
ComputeLayoutAndAllocationSize(parsedRect, format, optLayout));
Span<uint8_t> buffer;
MOZ_TRY_VAR(buffer, GetArrayBufferData(aBuffer).mapErr([](nsresult aError) {
return nsPrintfCString("Failed to get buffer data: %x",
static_cast<uint32_t>(aError));
}));
if (buffer.size_bytes() <
static_cast<size_t>(combinedLayout.mAllocationSize)) {
return Err(nsCString("data is too small"));
}
// TODO: If codedSize is (3, 3) and visibleRect is (0, 0, 1, 1) but the data
// is 2 x 2 RGBA buffer (2 x 2 x 4 bytes), it pass the above check. In this
// case, we can crop it to a 1 x 1-codedSize image (Bug 1782128).
if (buffer.size_bytes() < format.SampleCount(codedSize)) { // 1 byte/sample
return Err(nsCString("data is too small"));
}
// By spec, we should set visible* here. But if we don't change the image,
// visible* is same as parsedRect here. The display{Width, Height} is
// visible{Width, Height} if it's not set.
Maybe<uint64_t> duration = OptionalToMaybe(aInit.mDuration);
VideoColorSpaceInit colorSpace =
PickColorSpace(OptionalToPointer(aInit.mColorSpace), aInit.mFormat);
RefPtr<layers::Image> data;
MOZ_TRY_VAR(data,
CreateImageFromBuffer(format, colorSpace, codedSize, buffer));
MOZ_TRY_VAR(
data,
aBuffer.ProcessFixedData([&](const Span<uint8_t>& aData)
-> Result<RefPtr<layers::Image>, nsCString> {
if (aData.Length() <
static_cast<size_t>(combinedLayout.mAllocationSize)) {
return Err(nsCString("data is too small"));
}
// TODO: If codedSize is (3, 3) and visibleRect is (0, 0, 1, 1) but the
// data is 2 x 2 RGBA buffer (2 x 2 x 4 bytes), it pass the above check.
// In this case, we can crop it to a 1 x 1-codedSize image (Bug
// 1782128).
if (aData.Length() < format.SampleCount(codedSize)) { // 1 byte/sample
return Err(nsCString("data is too small"));
}
return CreateImageFromBuffer(format, colorSpace, codedSize,
Span(aData.Elements(), aData.Length()));
}));
MOZ_ASSERT(data);
MOZ_ASSERT(data->GetSize() == codedSize);
// By spec, we should set visible* here. But if we don't change the image,
// visible* is same as parsedRect here. The display{Width, Height} is
// visible{Width, Height} if it's not set.
// TODO: Spec should assign aInit.mFormat to inner format value:
// https://github.com/w3c/webcodecs/issues/509.
// This comment should be removed once the issue is resolved.
@ -1660,61 +1661,56 @@ already_AddRefed<Promise> VideoFrame::CopyTo(
}
layout = r1.unwrap();
auto r2 = GetSharedArrayBufferData(aDestination);
if (r2.isErr()) {
p->MaybeRejectWithTypeError("Failed to get buffer");
return p.forget();
}
Span<uint8_t> buffer = r2.unwrap();
if (buffer.size_bytes() < layout.mAllocationSize) {
p->MaybeRejectWithTypeError("Destination buffer is too small");
return p.forget();
}
Sequence<PlaneLayout> planeLayouts;
nsTArray<Format::Plane> planes = mResource->mFormat->Planes();
MOZ_ASSERT(layout.mComputedLayouts.Length() == planes.Length());
// TODO: These jobs can be run in a thread pool (bug 1780656) to unblock the
// current thread.
for (size_t i = 0; i < layout.mComputedLayouts.Length(); ++i) {
ComputedPlaneLayout& l = layout.mComputedLayouts[i];
uint32_t destinationOffset = l.mDestinationOffset;
PlaneLayout* pl = planeLayouts.AppendElement(fallible);
if (!pl) {
p->MaybeRejectWithTypeError("Out of memory");
return ProcessTypedArraysFixed(aDestination, [&](const Span<uint8_t>& aData) {
if (aData.size_bytes() < layout.mAllocationSize) {
p->MaybeRejectWithTypeError("Destination buffer is too small");
return p.forget();
}
pl->mOffset = l.mDestinationOffset;
pl->mStride = l.mDestinationStride;
// Copy pixels of `size` starting from `origin` on planes[i] to
// `aDestination`.
gfx::IntPoint origin(
l.mSourceLeftBytes / mResource->mFormat->SampleBytes(planes[i]),
l.mSourceTop);
gfx::IntSize size(
l.mSourceWidthBytes / mResource->mFormat->SampleBytes(planes[i]),
l.mSourceHeight);
if (!mResource->CopyTo(planes[i], {origin, size},
buffer.From(destinationOffset),
static_cast<size_t>(l.mDestinationStride))) {
p->MaybeRejectWithTypeError(
nsPrintfCString("Failed to copy image data in %s plane",
mResource->mFormat->PlaneName(planes[i])));
return p.forget();
Sequence<PlaneLayout> planeLayouts;
nsTArray<Format::Plane> planes = mResource->mFormat->Planes();
MOZ_ASSERT(layout.mComputedLayouts.Length() == planes.Length());
// TODO: These jobs can be run in a thread pool (bug 1780656) to unblock
// the current thread.
for (size_t i = 0; i < layout.mComputedLayouts.Length(); ++i) {
ComputedPlaneLayout& l = layout.mComputedLayouts[i];
uint32_t destinationOffset = l.mDestinationOffset;
PlaneLayout* pl = planeLayouts.AppendElement(fallible);
if (!pl) {
p->MaybeRejectWithTypeError("Out of memory");
return p.forget();
}
pl->mOffset = l.mDestinationOffset;
pl->mStride = l.mDestinationStride;
// Copy pixels of `size` starting from `origin` on planes[i] to
// `aDestination`.
gfx::IntPoint origin(
l.mSourceLeftBytes / mResource->mFormat->SampleBytes(planes[i]),
l.mSourceTop);
gfx::IntSize size(
l.mSourceWidthBytes / mResource->mFormat->SampleBytes(planes[i]),
l.mSourceHeight);
if (!mResource->CopyTo(planes[i], {origin, size},
aData.From(destinationOffset),
static_cast<size_t>(l.mDestinationStride))) {
p->MaybeRejectWithTypeError(
nsPrintfCString("Failed to copy image data in %s plane",
mResource->mFormat->PlaneName(planes[i])));
return p.forget();
}
}
}
MOZ_ASSERT(layout.mComputedLayouts.Length() == planes.Length());
// TODO: Spec doesn't resolve with a value. See
// https://github.com/w3c/webcodecs/issues/510 This comment should be removed
// once the issue is resolved.
p->MaybeResolve(planeLayouts);
return p.forget();
MOZ_ASSERT(layout.mComputedLayouts.Length() == planes.Length());
// TODO: Spec doesn't resolve with a value. See
// https://github.com/w3c/webcodecs/issues/510 This comment should be
// removed once the issue is resolved.
p->MaybeResolve(planeLayouts);
return p.forget();
});
}
// https://w3c.github.io/webcodecs/#dom-videoframe-clone

View File

@ -46,40 +46,6 @@ nsTArray<nsCString> GuessContainers(const nsAString& aCodec) {
* The below are helpers to operate ArrayBuffer or ArrayBufferView.
*/
template <class T>
Result<Span<uint8_t>, nsresult> GetArrayBufferData(const T& aBuffer) {
// Get buffer's data and length before using it.
aBuffer.ComputeState();
CheckedInt<size_t> byteLength(sizeof(typename T::element_type));
byteLength *= aBuffer.Length();
if (NS_WARN_IF(!byteLength.isValid())) {
return Err(NS_ERROR_INVALID_ARG);
}
return Span<uint8_t>(aBuffer.Data(), byteLength.value());
}
Result<Span<uint8_t>, nsresult> GetSharedArrayBufferData(
const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer) {
if (aBuffer.IsArrayBufferView()) {
return GetArrayBufferData(aBuffer.GetAsArrayBufferView());
}
MOZ_ASSERT(aBuffer.IsArrayBuffer());
return GetArrayBufferData(aBuffer.GetAsArrayBuffer());
}
Result<Span<uint8_t>, nsresult> GetSharedArrayBufferData(
const OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer) {
if (aBuffer.IsArrayBufferView()) {
return GetArrayBufferData(aBuffer.GetAsArrayBufferView());
}
MOZ_ASSERT(aBuffer.IsArrayBuffer());
return GetArrayBufferData(aBuffer.GetAsArrayBuffer());
}
static std::tuple<JS::ArrayBufferOrView, size_t, size_t> GetArrayBufferInfo(
JSContext* aCx,
const OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer) {

View File

@ -73,12 +73,6 @@ Nullable<T> MaybeToNullable(const Maybe<T>& aOptional) {
* Below are helpers to operate ArrayBuffer or ArrayBufferView.
*/
Result<Span<uint8_t>, nsresult> GetSharedArrayBufferData(
const MaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer);
Result<Span<uint8_t>, nsresult> GetSharedArrayBufferData(
const OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer);
Result<Ok, nsresult> CloneBuffer(
JSContext* aCx,
OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aDest,

View File

@ -6,6 +6,7 @@
#include "jsapi/RTCEncodedFrameBase.h"
#include "js/GCAPI.h"
#include "nsIGlobalObject.h"
#include "mozilla/dom/ScriptSettings.h"
#include "js/ArrayBuffer.h"
@ -45,9 +46,10 @@ unsigned long RTCEncodedFrameBase::Timestamp() const { return mTimestamp; }
void RTCEncodedFrameBase::SetData(const ArrayBuffer& aData) {
mData.set(aData.Obj());
if (mFrame) {
aData.ComputeState();
mFrame->SetData(rtc::ArrayView<const uint8_t>(
static_cast<const uint8_t*>(aData.Data()), aData.Length()));
aData.ProcessData([&](const Span<uint8_t>& aData, JS::AutoCheckCannotGC&&) {
mFrame->SetData(
rtc::ArrayView<const uint8_t>(aData.Elements(), aData.Length()));
});
}
}

View File

@ -4,6 +4,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "js/Realm.h"
#include "mozilla/dom/EventBinding.h"
#include "mozilla/dom/MIDIMessageEvent.h"
#include "mozilla/dom/MIDIMessageEventBinding.h"
@ -63,10 +64,10 @@ already_AddRefed<MIDIMessageEvent> MIDIMessageEvent::Constructor(
// Set data for event. Timestamp will always be set to Now() (default for
// event) using this constructor.
if (aEventInitDict.mData.WasPassed()) {
const auto& a = aEventInitDict.mData.Value();
a.ComputeState();
e->mData =
Uint8Array::Create(aGlobal.Context(), owner, a.Length(), a.Data());
JSAutoRealm ar(aGlobal.Context(), aGlobal.Get());
JS::Rooted<JSObject*> data(aGlobal.Context(),
aEventInitDict.mData.Value().Obj());
e->mData = JS_NewUint8ArrayFromArray(aGlobal.Context(), data);
if (NS_WARN_IF(!e->mData)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return nullptr;

View File

@ -198,50 +198,44 @@ already_AddRefed<Promise> WritableStreamToOutput::WriteCallback(
return nullptr;
}
// This is a duplicate of dom/encoding/TextDecoderStream.cpp#51-69
// PeterV will deal with that when he lands his patch for TypedArrays
auto dataSpan = [&data]() {
if (data.IsArrayBuffer()) {
const ArrayBuffer& buffer = data.GetAsArrayBuffer();
buffer.ComputeState();
return Span{buffer.Data(), buffer.Length()};
}
MOZ_ASSERT(data.IsArrayBufferView());
const ArrayBufferView& buffer = data.GetAsArrayBufferView();
buffer.ComputeState();
return Span{buffer.Data(), buffer.Length()};
}();
// Try to write first, and only enqueue data if we were already blocked
// or the write didn't write it all. This avoids allocations and copies
// in common cases.
MOZ_ASSERT(!mPromise);
MOZ_ASSERT(mWritten == 0);
uint32_t written = 0;
nsresult rv = mOutput->Write(mozilla::AsChars(dataSpan).Elements(),
dataSpan.Length(), &written);
if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
promise->MaybeRejectWithAbortError("error writing data");
return promise.forget();
}
if (NS_SUCCEEDED(rv)) {
if (written == dataSpan.Length()) {
promise->MaybeResolveWithUndefined();
return promise.forget();
ProcessTypedArraysFixed(data, [&](const Span<uint8_t>& aData) {
Span<uint8_t> dataSpan = aData;
nsresult rv = mOutput->Write(mozilla::AsChars(dataSpan).Elements(),
dataSpan.Length(), &written);
if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
promise->MaybeRejectWithAbortError("error writing data");
return;
}
dataSpan = dataSpan.From(written);
if (NS_SUCCEEDED(rv)) {
if (written == dataSpan.Length()) {
promise->MaybeResolveWithUndefined();
return;
}
dataSpan = dataSpan.From(written);
}
auto buffer = Buffer<uint8_t>::CopyFrom(dataSpan);
if (buffer.isNothing()) {
promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
return;
}
mData = std::move(buffer);
});
if (promise->State() != Promise::PromiseState::Pending) {
return promise.forget();
}
auto buffer = Buffer<uint8_t>::CopyFrom(dataSpan);
if (buffer.isNothing()) {
promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
return promise.forget();
}
mData = std::move(buffer);
mPromise = promise;
nsCOMPtr<nsIEventTarget> target = mozilla::GetCurrentSerialEventTarget();
rv = mOutput->AsyncWait(this, 0, 0, target);
nsresult rv = mOutput->AsyncWait(this, 0, 0, target);
if (NS_FAILED(rv)) {
ClearData();
promise->MaybeRejectWithUnknownError("error waiting to write data");

View File

@ -2775,51 +2775,51 @@ void SyncReadFile::ReadBytesInto(const Uint8Array& aDestArray,
return aRv.ThrowOperationError("SyncReadFile is closed");
}
aDestArray.ComputeState();
auto rangeEnd = CheckedInt64(aOffset) + aDestArray.Length();
if (!rangeEnd.isValid()) {
return aRv.ThrowOperationError("Requested range overflows i64");
}
if (rangeEnd.value() > mSize) {
return aRv.ThrowOperationError(
"Requested range overflows SyncReadFile size");
}
uint32_t readLen{aDestArray.Length()};
if (readLen == 0) {
return;
}
if (nsresult rv = mStream->Seek(PR_SEEK_SET, aOffset); NS_FAILED(rv)) {
return aRv.ThrowOperationError(
FormatErrorMessage(rv, "Could not seek to position %lld", aOffset));
}
Span<char> toRead(reinterpret_cast<char*>(aDestArray.Data()), readLen);
uint32_t totalRead = 0;
while (totalRead != readLen) {
// Read no more than INT32_MAX on each call to mStream->Read, otherwise it
// returns an error.
uint32_t bytesToReadThisChunk =
std::min<uint32_t>(readLen - totalRead, INT32_MAX);
uint32_t bytesRead = 0;
if (nsresult rv =
mStream->Read(toRead.Elements(), bytesToReadThisChunk, &bytesRead);
NS_FAILED(rv)) {
return aRv.ThrowOperationError(FormatErrorMessage(
rv, "Encountered an unexpected error while reading file stream"));
aDestArray.ProcessFixedData([&](const Span<uint8_t>& aData) {
auto rangeEnd = CheckedInt64(aOffset) + aData.Length();
if (!rangeEnd.isValid()) {
return aRv.ThrowOperationError("Requested range overflows i64");
}
if (bytesRead == 0) {
if (rangeEnd.value() > mSize) {
return aRv.ThrowOperationError(
"Reading stopped before the entire array was filled");
"Requested range overflows SyncReadFile size");
}
totalRead += bytesRead;
toRead = toRead.From(bytesRead);
}
size_t readLen{aData.Length()};
if (readLen == 0) {
return;
}
if (nsresult rv = mStream->Seek(PR_SEEK_SET, aOffset); NS_FAILED(rv)) {
return aRv.ThrowOperationError(
FormatErrorMessage(rv, "Could not seek to position %lld", aOffset));
}
Span<char> toRead = AsWritableChars(aData);
size_t totalRead = 0;
while (totalRead != readLen) {
// Read no more than INT32_MAX on each call to mStream->Read,
// otherwise it returns an error.
uint32_t bytesToReadThisChunk =
std::min(readLen - totalRead, size_t(INT32_MAX));
uint32_t bytesRead = 0;
if (nsresult rv = mStream->Read(toRead.Elements(), bytesToReadThisChunk,
&bytesRead);
NS_FAILED(rv)) {
return aRv.ThrowOperationError(FormatErrorMessage(
rv, "Encountered an unexpected error while reading file stream"));
}
if (bytesRead == 0) {
return aRv.ThrowOperationError(
"Reading stopped before the entire array was filled");
}
totalRead += bytesRead;
toRead = toRead.From(bytesRead);
}
});
}
void SyncReadFile::Close() { mStream = nullptr; }

View File

@ -56,123 +56,49 @@ already_AddRefed<dom::Promise> Queue::OnSubmittedWorkDone(ErrorResult& aRv) {
return promise.forget();
}
// Get the base address and length of part of a `BufferSource`.
//
// Given `aBufferSource` and an offset `aDataOffset` and optional length
// `aSizeOrRemainder` describing the range of its contents we want to see, check
// all arguments and set `aDataContents` and `aContentsSize` to a pointer to the
// bytes and a length. Report errors in `aRv`.
//
// If `ASizeOrRemainder` was not passed, return a view from the starting offset
// to the end of `aBufferSource`.
//
// On success, the returned `aDataContents` is never `nullptr`. If the
// `ArrayBuffer` is detached, return a pointer to a dummy buffer and set
// `aContentsSize` to zero.
//
// The `aBufferSource` argument is a WebIDL `BufferSource`, which WebGPU methods
// use anywhere they accept a block of raw bytes. WebIDL defines `BufferSource`
// as:
//
// typedef (ArrayBufferView or ArrayBuffer) BufferSource;
//
// This appears in Gecko code as `dom::ArrayBufferViewOrArrayBuffer`.
static void GetBufferSourceDataAndSize(
const dom::ArrayBufferViewOrArrayBuffer& aBufferSource,
uint64_t aDataOffset, const dom::Optional<uint64_t>& aSizeOrRemainder,
uint8_t*& aDataContents, uint64_t& aContentsSize, const char* aOffsetName,
ErrorResult& aRv) {
uint64_t dataSize = 0;
uint8_t* dataContents = nullptr;
if (aBufferSource.IsArrayBufferView()) {
const auto& view = aBufferSource.GetAsArrayBufferView();
view.ComputeState();
dataSize = view.Length();
dataContents = view.Data();
}
if (aBufferSource.IsArrayBuffer()) {
const auto& ab = aBufferSource.GetAsArrayBuffer();
ab.ComputeState();
dataSize = ab.Length();
dataContents = ab.Data();
}
if (aDataOffset > dataSize) {
aRv.ThrowOperationError(
nsPrintfCString("%s is greater than data length", aOffsetName));
return;
}
uint64_t contentsSize = 0;
if (aSizeOrRemainder.WasPassed()) {
contentsSize = aSizeOrRemainder.Value();
} else {
// We already know that aDataOffset <= length, so this cannot underflow.
contentsSize = dataSize - aDataOffset;
}
// This could be folded into the if above, but it's nice to make it
// obvious that the check always occurs.
// We already know that aDataOffset <= length, so this cannot underflow.
if (contentsSize > dataSize - aDataOffset) {
aRv.ThrowOperationError(
nsPrintfCString("%s + size is greater than data length", aOffsetName));
return;
}
if (!dataContents) {
// Passing `nullptr` as either the source or destination to
// `memcpy` is undefined behavior, even when the count is zero:
//
// https://en.cppreference.com/w/cpp/string/byte/memcpy
//
// We can either make callers responsible for checking the pointer
// before calling `memcpy`, or we can have it point to a
// permanently-live `static` dummy byte, so that the copies are
// harmless. The latter seems less error-prone.
static uint8_t dummy;
dataContents = &dummy;
MOZ_RELEASE_ASSERT(contentsSize == 0);
}
aDataContents = dataContents;
aContentsSize = contentsSize;
}
void Queue::WriteBuffer(const Buffer& aBuffer, uint64_t aBufferOffset,
const dom::ArrayBufferViewOrArrayBuffer& aData,
uint64_t aDataOffset,
const dom::Optional<uint64_t>& aSize,
ErrorResult& aRv) {
uint8_t* dataContents = nullptr;
uint64_t contentsSize = 0;
GetBufferSourceDataAndSize(aData, aDataOffset, aSize, dataContents,
contentsSize, "dataOffset", aRv);
if (aRv.Failed()) {
return;
}
dom::ProcessTypedArraysFixed(aData, [&](const Span<const uint8_t>& aData) {
uint64_t length = aData.Length();
const auto checkedSize = aSize.WasPassed()
? CheckedInt<size_t>(aSize.Value())
: CheckedInt<size_t>(length) - aDataOffset;
if (!checkedSize.isValid()) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
if (contentsSize % 4 != 0) {
aRv.ThrowAbortError("Byte size must be a multiple of 4");
return;
}
const auto& size = checkedSize.value();
if (aDataOffset + size > length) {
aRv.ThrowAbortError(nsPrintfCString("Wrong data size %" PRIuPTR, size));
return;
}
auto alloc =
mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(contentsSize);
if (alloc.isNothing()) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
if (size % 4 != 0) {
aRv.ThrowAbortError("Byte size must be a multiple of 4");
return;
}
auto handle = std::move(alloc.ref().first);
auto mapping = std::move(alloc.ref().second);
auto alloc = mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(size);
if (alloc.isNothing()) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
memcpy(mapping.Bytes().data(), dataContents + aDataOffset, contentsSize);
ipc::ByteBuf bb;
ffi::wgpu_queue_write_buffer(aBuffer.mId, aBufferOffset, ToFFI(&bb));
if (!mBridge->SendQueueWriteAction(mId, mParent->mId, std::move(bb),
std::move(handle))) {
MOZ_CRASH("IPC failure");
}
auto handle = std::move(alloc.ref().first);
auto mapping = std::move(alloc.ref().second);
memcpy(mapping.Bytes().data(), aData.Elements() + aDataOffset, size);
ipc::ByteBuf bb;
ffi::wgpu_queue_write_buffer(aBuffer.mId, aBufferOffset, ToFFI(&bb));
if (!mBridge->SendQueueWriteAction(mId, mParent->mId, std::move(bb),
std::move(handle))) {
MOZ_CRASH("IPC failure");
}
});
}
void Queue::WriteTexture(const dom::GPUImageCopyTexture& aDestination,
@ -187,40 +113,39 @@ void Queue::WriteTexture(const dom::GPUImageCopyTexture& aDestination,
ffi::WGPUExtent3d extent = {};
ConvertExtent3DToFFI(aSize, &extent);
uint8_t* dataContents = nullptr;
uint64_t contentsSize = 0;
GetBufferSourceDataAndSize(aData, aDataLayout.mOffset,
dom::Optional<uint64_t>(), dataContents,
contentsSize, "dataLayout.offset", aRv);
if (aRv.Failed()) {
return;
}
dom::ProcessTypedArraysFixed(aData, [&](const Span<const uint8_t>& aData) {
if (aData.IsEmpty()) {
aRv.ThrowAbortError("Input size cannot be zero.");
return;
}
if (!contentsSize) {
aRv.ThrowAbortError("Input size cannot be zero.");
return;
}
MOZ_ASSERT(dataContents != nullptr);
const auto checkedSize =
CheckedInt<size_t>(aData.Length()) - aDataLayout.mOffset;
if (!checkedSize.isValid()) {
aRv.ThrowAbortError("Offset is higher than the size");
return;
}
const auto size = checkedSize.value();
auto alloc =
mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(contentsSize);
if (alloc.isNothing()) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
auto alloc = mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(size);
if (alloc.isNothing()) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
auto handle = std::move(alloc.ref().first);
auto mapping = std::move(alloc.ref().second);
auto handle = std::move(alloc.ref().first);
auto mapping = std::move(alloc.ref().second);
memcpy(mapping.Bytes().data(), dataContents + aDataLayout.mOffset,
contentsSize);
memcpy(mapping.Bytes().data(), aData.Elements() + aDataLayout.mOffset,
size);
ipc::ByteBuf bb;
ffi::wgpu_queue_write_texture(copyView, dataLayout, extent, ToFFI(&bb));
if (!mBridge->SendQueueWriteAction(mId, mParent->mId, std::move(bb),
std::move(handle))) {
MOZ_CRASH("IPC failure");
}
ipc::ByteBuf bb;
ffi::wgpu_queue_write_texture(copyView, dataLayout, extent, ToFFI(&bb));
if (!mBridge->SendQueueWriteAction(mId, mParent->mId, std::move(bb),
std::move(handle))) {
MOZ_CRASH("IPC failure");
}
});
}
static WebGLTexelFormat ToWebGLTexelFormat(gfx::SurfaceFormat aFormat) {

View File

@ -2389,7 +2389,7 @@ void WebSocket::Send(const ArrayBuffer& aData, ErrorResult& aRv) {
AssertIsOnTargetThread();
static_assert(
sizeof(std::remove_reference<decltype(aData)>::type::element_type) == 1,
sizeof(std::remove_reference_t<decltype(aData)>::element_type) == 1,
"byte-sized data required");
nsCString msgString;
@ -2404,7 +2404,7 @@ void WebSocket::Send(const ArrayBufferView& aData, ErrorResult& aRv) {
AssertIsOnTargetThread();
static_assert(
sizeof(std::remove_reference<decltype(aData)>::type::element_type) == 1,
sizeof(std::remove_reference_t<decltype(aData)>::element_type) == 1,
"byte-sized data required");
nsCString msgString;

View File

@ -18,33 +18,36 @@ OwnedRustBuffer::OwnedRustBuffer(const RustBuffer& aBuf) {
Result<OwnedRustBuffer, nsCString> OwnedRustBuffer::FromArrayBuffer(
const ArrayBuffer& aArrayBuffer) {
if (aArrayBuffer.Length() > INT32_MAX) {
return Err("Input ArrayBuffer is too large"_ns);
}
return aArrayBuffer.ProcessData(
[](const Span<uint8_t>& aData,
JS::AutoCheckCannotGC&&) -> Result<OwnedRustBuffer, nsCString> {
if (aData.Length() > INT32_MAX) {
return Err("Input ArrayBuffer is too large"_ns);
}
RustCallStatus status{};
RustBuffer buf = uniffi_rustbuffer_alloc(
static_cast<int32_t>(aArrayBuffer.Length()), &status);
buf.len = aArrayBuffer.Length();
if (status.code != 0) {
if (status.error_buf.data) {
auto message = nsCString("uniffi_rustbuffer_alloc: ");
message.Append(
nsDependentCSubstring(reinterpret_cast<char*>(status.error_buf.data),
status.error_buf.len));
RustCallStatus status2{};
uniffi_rustbuffer_free(status.error_buf, &status2);
MOZ_RELEASE_ASSERT(status2.code == 0,
"Freeing a rustbuffer should never fail");
return Err(message);
RustCallStatus status{};
RustBuffer buf = uniffi_rustbuffer_alloc(
static_cast<int32_t>(aData.Length()), &status);
buf.len = aData.Length();
if (status.code != 0) {
if (status.error_buf.data) {
auto message = nsCString("uniffi_rustbuffer_alloc: ");
message.Append(nsDependentCSubstring(
reinterpret_cast<char*>(status.error_buf.data),
status.error_buf.len));
RustCallStatus status2{};
uniffi_rustbuffer_free(status.error_buf, &status2);
MOZ_RELEASE_ASSERT(status2.code == 0,
"Freeing a rustbuffer should never fail");
return Err(message);
}
} else {
return Err("Unknown error allocating rust buffer"_ns);
}
}
return Err("Unknown error allocating rust buffer"_ns);
}
memcpy(buf.data, aArrayBuffer.Data(), buf.len);
return OwnedRustBuffer(buf);
memcpy(buf.data, aData.Elements(), buf.len);
return OwnedRustBuffer(buf);
});
}
OwnedRustBuffer::OwnedRustBuffer(OwnedRustBuffer&& aOther) : mBuf(aOther.mBuf) {

View File

@ -139,9 +139,7 @@ class ScaffoldingConverter<RustBuffer> {
return Err("Bad argument type"_ns);
}
const dom::ArrayBuffer& arrayBuf = aValue.GetAsArrayBuffer();
arrayBuf.ComputeState();
return OwnedRustBuffer::FromArrayBuffer(arrayBuf);
return OwnedRustBuffer::FromArrayBuffer(aValue.GetAsArrayBuffer());
}
static RustBuffer IntoRust(OwnedRustBuffer&& aValue) {

View File

@ -71,18 +71,19 @@ void UniFFIPointer::Write(const ArrayBuffer& aArrayBuff, uint32_t aPosition,
}
MOZ_LOG(sUniFFIPointerLogger, LogLevel::Info,
("[UniFFI] Writing Pointer to buffer"));
aArrayBuff.ComputeState();
CheckedUint32 position = aPosition;
CheckedUint32 end = position + 8;
if (!end.isValid() || end.value() > aArrayBuff.Length()) {
aError.ThrowRangeError("position is out of range");
return;
}
// in Rust and Read(), a u64 is read as BigEndian and then converted to a
// pointer we do the reverse here
uint8_t* data_ptr = aArrayBuff.Data() +
aPosition; // Pointer arithmetic, move by position bytes
mozilla::BigEndian::writeUint64(data_ptr, (uint64_t)GetPtr());
aArrayBuff.ProcessData([&](const Span<uint8_t>& aData,
JS::AutoCheckCannotGC&&) {
CheckedUint32 end = aPosition + 8;
if (!end.isValid() || end.value() > aData.Length()) {
aError.ThrowRangeError("position is out of range");
return;
}
// in Rust and Read(), a u64 is read as BigEndian and then converted to
// a pointer we do the reverse here
const auto& data_ptr = aData.Subspan(aPosition, 8);
mozilla::BigEndian::writeUint64(data_ptr.Elements(), (uint64_t)GetPtr());
});
}
UniFFIPointer::UniFFIPointer(void* aPtr, const UniFFIPointerType* aType) {