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 ArrayBufferViewOrArrayBuffer& aSource,
const Base64URLEncodeOptions& aOptions, const Base64URLEncodeOptions& aOptions,
nsACString& aResult, ErrorResult& aRv) { 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 auto paddingPolicy = aOptions.mPad ? Base64URLEncodePaddingPolicy::Include
: Base64URLEncodePaddingPolicy::Omit; : Base64URLEncodePaddingPolicy::Omit;
nsresult rv = mozilla::Base64URLEncode(length, data, paddingPolicy, aResult); ProcessTypedArrays(
if (NS_WARN_IF(NS_FAILED(rv))) { aSource, [&](const Span<uint8_t>& aData, JS::AutoCheckCannotGC&&) {
aResult.Truncate(); nsresult rv = mozilla::Base64URLEncode(aData.Length(), aData.Elements(),
aRv.Throw(rv); paddingPolicy, aResult);
} if (NS_WARN_IF(NS_FAILED(rv))) {
aResult.Truncate();
aRv.Throw(rv);
}
});
} }
/* static */ /* static */

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -415,18 +415,6 @@ struct TypedArray_base : public SpiderMonkeyInterfaceObjectStorage,
return mData; 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 { inline uint32_t Length() const {
MOZ_ASSERT(mComputed); MOZ_ASSERT(mComputed);
return mLength; return mLength;
@ -714,6 +702,7 @@ struct TypedArray_base : public SpiderMonkeyInterfaceObjectStorage,
return CallProcessor(GetCurrentData(), std::forward<Processor>(aProcessor)); return CallProcessor(GetCurrentData(), std::forward<Processor>(aProcessor));
} }
public:
inline void Reset() { inline void Reset() {
// This method mostly exists to inform the GC rooting hazard analysis that // 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 // 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) { nsIPrincipal& aSubjectPrincipal, JSObject** aRetval) {
MOZ_ASSERT(aWidth && aHeight); MOZ_ASSERT(aWidth && aHeight);
// Restrict the typed array length to INT32_MAX because that's all we support // Restrict the typed array length to INT32_MAX because that's all we support.
// in dom::TypedArray::ComputeState.
CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aWidth) * aHeight * 4; CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aWidth) * aHeight * 4;
if (!len.isValid() || len.value() > INT32_MAX) { if (!len.isValid() || len.value() > INT32_MAX) {
return NS_ERROR_DOM_INDEX_SIZE_ERR; return NS_ERROR_DOM_INDEX_SIZE_ERR;
@ -6137,18 +6136,13 @@ void CanvasRenderingContext2D::PutImageData_explicit(
return; return;
} }
arr.ComputeState(); RefPtr<DataSourceSurface> sourceSurface;
uint8_t* lockedBits = nullptr;
uint32_t dataLen = arr.Length(); // The canvas spec says that the current path, transformation matrix,
// shadow attributes, global alpha, the clipping region, and global
uint32_t len = width * height * 4; // composition operator must not affect the getImageData() and
if (dataLen != len) { // putImageData() methods.
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.
const gfx::Rect putRect(dirtyRect); const gfx::Rect putRect(dirtyRect);
EnsureTarget(&putRect); EnsureTarget(&putRect);
@ -6157,8 +6151,6 @@ void CanvasRenderingContext2D::PutImageData_explicit(
} }
DataSourceSurface::MappedSurface map; DataSourceSurface::MappedSurface map;
RefPtr<DataSourceSurface> sourceSurface;
uint8_t* lockedBits = nullptr;
uint8_t* dstData; uint8_t* dstData;
IntSize dstSize; IntSize dstSize;
int32_t dstStride; int32_t dstStride;
@ -6169,10 +6161,11 @@ void CanvasRenderingContext2D::PutImageData_explicit(
sourceSurface = Factory::CreateDataSourceSurface( sourceSurface = Factory::CreateDataSourceSurface(
dirtyRect.Size(), SurfaceFormat::B8G8R8A8, false); dirtyRect.Size(), SurfaceFormat::B8G8R8A8, false);
// In certain scenarios, requesting larger than 8k image fails. Bug 803568 // In certain scenarios, requesting larger than 8k image fails. Bug
// covers the details of how to run into it, but the full detailed // 803568 covers the details of how to run into it, but the full
// investigation hasn't been done to determine the underlying cause. We // detailed investigation hasn't been done to determine the
// will just handle the failure to allocate the surface to avoid a crash. // underlying cause. We will just handle the failure to allocate
// the surface to avoid a crash.
if (!sourceSurface) { if (!sourceSurface) {
return aRv.Throw(NS_ERROR_FAILURE); return aRv.Throw(NS_ERROR_FAILURE);
} }
@ -6188,12 +6181,27 @@ void CanvasRenderingContext2D::PutImageData_explicit(
dstFormat = sourceSurface->GetFormat(); 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( uint8_t* srcData =
srcData, width * 4, SurfaceFormat::R8G8B8A8, dstData, dstStride, aData.Elements() + srcRect.y * (width * 4) + srcRect.x * 4;
mOpaque ? SurfaceFormat::X8R8G8B8_UINT32 : SurfaceFormat::A8R8G8B8_UINT32,
dirtyRect.Size()); PremultiplyData(srcData, width * 4, SurfaceFormat::R8G8B8A8, dstData,
dstStride,
mOpaque ? SurfaceFormat::X8R8G8B8_UINT32
: SurfaceFormat::A8R8G8B8_UINT32,
dirtyRect.Size());
});
if (aRv.Failed()) {
return;
}
if (lockedBits) { if (lockedBits) {
mTarget->ReleaseBits(lockedBits); mTarget->ReleaseBits(lockedBits);

View File

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

View File

@ -7,9 +7,11 @@
#define CLIENTWEBGLCONTEXT_H_ #define CLIENTWEBGLCONTEXT_H_
#include "GLConsts.h" #include "GLConsts.h"
#include "js/GCAPI.h"
#include "mozilla/dom/ImageData.h" #include "mozilla/dom/ImageData.h"
#include "mozilla/Range.h" #include "mozilla/Range.h"
#include "mozilla/RefCounted.h" #include "mozilla/RefCounted.h"
#include "mozilla/dom/TypedArray.h"
#include "nsICanvasRenderingContextInternal.h" #include "nsICanvasRenderingContextInternal.h"
#include "nsWrapperCache.h" #include "nsWrapperCache.h"
#include "mozilla/dom/WebGLRenderingContextBinding.h" #include "mozilla/dom/WebGLRenderingContextBinding.h"
@ -24,6 +26,7 @@
#include "WebGLCommandQueue.h" #include "WebGLCommandQueue.h"
#include <memory> #include <memory>
#include <type_traits>
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
@ -618,22 +621,47 @@ using Float32ListU = dom::MaybeSharedFloat32ArrayOrUnrestrictedFloatSequence;
using Int32ListU = dom::MaybeSharedInt32ArrayOrLongSequence; using Int32ListU = dom::MaybeSharedInt32ArrayOrLongSequence;
using Uint32ListU = dom::MaybeSharedUint32ArrayOrUnsignedLongSequence; using Uint32ListU = dom::MaybeSharedUint32ArrayOrUnsignedLongSequence;
inline Range<const float> MakeRange(const Float32ListU& list) { template <typename Converter, typename T>
if (list.IsFloat32Array()) return MakeRangeAbv(list.GetAsFloat32Array()); inline bool ConvertSequence(const dom::Sequence<T>& sequence,
Converter&& converter) {
return MakeRange(list.GetAsUnrestrictedFloatSequence()); // 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) { template <typename Converter>
if (list.IsInt32Array()) return MakeRangeAbv(list.GetAsInt32Array()); 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) { template <typename Converter>
if (list.IsUint32Array()) return MakeRangeAbv(list.GetAsUint32Array()); 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> template <typename T>
@ -644,6 +672,11 @@ inline Range<const uint8_t> MakeByteRange(const T& x) {
typed.length() * sizeof(typed[0])); 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 { struct TexImageSourceAdapter final : public TexImageSource {
@ -898,11 +931,11 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
void EnqueueErrorImpl(GLenum errorOrZero, const nsACString&) const; void EnqueueErrorImpl(GLenum errorOrZero, const nsACString&) const;
public: public:
bool ValidateArrayBufferView(const dom::ArrayBufferView& view, Maybe<Range<uint8_t>> ValidateArrayBufferView(const Span<uint8_t>& bytes,
GLuint elemOffset, GLuint elemCountOverride, size_t elemSize,
const GLenum errorEnum, GLuint elemOffset,
uint8_t** const out_bytes, GLuint elemCountOverride,
size_t* const out_byteLen) const; const GLenum errorEnum) const;
protected: protected:
template <typename T> template <typename T>
@ -1297,24 +1330,48 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
// - // -
private: private:
void ClearBufferTv(GLenum buffer, GLint drawBuffer, webgl::AttribBaseType, void ClearBufferTv(const GLenum buffer, const GLint drawBuffer,
const Range<const uint8_t>& view, GLuint srcElemOffset); const webgl::AttribBaseType type,
JS::AutoCheckCannotGC&& nogc,
const Span<const uint8_t>& view,
const GLuint srcElemOffset);
public: public:
void ClearBufferfv(GLenum buffer, GLint drawBuffer, const Float32ListU& list, void ClearBufferfv(GLenum buffer, GLint drawBuffer, const Float32ListU& list,
GLuint srcElemOffset) { GLuint srcElemOffset) {
ClearBufferTv(buffer, drawBuffer, webgl::AttribBaseType::Float, const FuncScope funcScope(*this, "clearBufferfv");
MakeByteRange(list), srcElemOffset); 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, void ClearBufferiv(GLenum buffer, GLint drawBuffer, const Int32ListU& list,
GLuint srcElemOffset) { GLuint srcElemOffset) {
ClearBufferTv(buffer, drawBuffer, webgl::AttribBaseType::Int, const FuncScope funcScope(*this, "clearBufferiv");
MakeByteRange(list), srcElemOffset); 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, void ClearBufferuiv(GLenum buffer, GLint drawBuffer, const Uint32ListU& list,
GLuint srcElemOffset) { GLuint srcElemOffset) {
ClearBufferTv(buffer, drawBuffer, webgl::AttribBaseType::Uint, const FuncScope funcScope(*this, "clearBufferuiv");
MakeByteRange(list), srcElemOffset); 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(); 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, void UniformData(GLenum funcElemType, const WebGLUniformLocationJS* const loc,
bool transpose, const Range<const uint8_t>& bytes, 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) \ #define _(NT, TypeListU, TYPE) \
void Uniform##NT##v(const WebGLUniformLocationJS* const loc, \ void Uniform##NT##v(const WebGLUniformLocationJS* const loc, \
const TypeListU& list, GLuint elemOffset = 0, \ const TypeListU& list, GLuint elemOffset = 0, \
GLuint elemCountOverride = 0) const { \ GLuint elemCountOverride = 0) const { \
UniformData(TYPE, loc, false, MakeByteRange(list), elemOffset, \ Convert(list, [&](const auto& aData, JS::AutoCheckCannotGC&& nogc) { \
elemCountOverride); \ UniformData(TYPE, loc, false, MakeByteRange(aData), std::move(nogc), \
elemOffset, elemCountOverride); \
return true; \
}); \
} }
_(1f, Float32ListU, LOCAL_GL_FLOAT) _(1f, Float32ListU, LOCAL_GL_FLOAT)
@ -1927,8 +2004,12 @@ class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
void UniformMatrix##X##fv(const WebGLUniformLocationJS* loc, bool transpose, \ void UniformMatrix##X##fv(const WebGLUniformLocationJS* loc, bool transpose, \
const Float32ListU& list, GLuint elemOffset = 0, \ const Float32ListU& list, GLuint elemOffset = 0, \
GLuint elemCountOverride = 0) const { \ GLuint elemCountOverride = 0) const { \
UniformData(LOCAL_GL_FLOAT_MAT##X, loc, transpose, MakeByteRange(list), \ Convert(list, [&](const Span<const float>& aData, \
elemOffset, elemCountOverride); \ JS::AutoCheckCannotGC&& nogc) { \
UniformData(LOCAL_GL_FLOAT_MAT##X, loc, transpose, MakeByteRange(aData), \
std::move(nogc), elemOffset, elemCountOverride); \
return true; \
}); \
} }
_(2) _(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) { void VertexAttrib1fv(const GLuint index, const Float32ListU& list) {
const FuncScope funcScope(*this, "vertexAttrib1fv"); const FuncScope funcScope(*this, "vertexAttrib1fv");
if (IsContextLost()) return; if (IsContextLost()) return;
const auto range = MakeRange(list); float arr[1];
if (range.length() < 1) { if (!MakeArrayFromList(list, arr)) {
EnqueueError(LOCAL_GL_INVALID_VALUE, "Length of `list` must be >=1.");
return; return;
} }
VertexAttrib1f(index, arr[0]);
VertexAttrib1f(index, range[0]);
} }
void VertexAttrib2fv(const GLuint index, const Float32ListU& list) { void VertexAttrib2fv(const GLuint index, const Float32ListU& list) {
const FuncScope funcScope(*this, "vertexAttrib1fv"); const FuncScope funcScope(*this, "vertexAttrib1fv");
if (IsContextLost()) return; if (IsContextLost()) return;
const auto range = MakeRange(list); float arr[2];
if (range.length() < 2) { if (!MakeArrayFromList(list, arr)) {
EnqueueError(LOCAL_GL_INVALID_VALUE, "Length of `list` must be >=2.");
return; return;
} }
VertexAttrib2f(index, arr[0], arr[1]);
VertexAttrib2f(index, range[0], range[1]);
} }
void VertexAttrib3fv(const GLuint index, const Float32ListU& list) { void VertexAttrib3fv(const GLuint index, const Float32ListU& list) {
const FuncScope funcScope(*this, "vertexAttrib1fv"); const FuncScope funcScope(*this, "vertexAttrib1fv");
if (IsContextLost()) return; if (IsContextLost()) return;
const auto range = MakeRange(list); float arr[3];
if (range.length() < 3) { if (!MakeArrayFromList(list, arr)) {
EnqueueError(LOCAL_GL_INVALID_VALUE, "Length of `list` must be >=3.");
return; return;
} }
VertexAttrib3f(index, arr[0], arr[1], arr[2]);
VertexAttrib3f(index, range[0], range[1], range[2]);
} }
void VertexAttrib4fv(GLuint index, const Float32ListU& list) { 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) { 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) { 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) { 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> template <typename MethodType, MethodType method, typename... Args>
void Run(Args&&... aArgs) const; 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 // Helpers for DOM operations, composition, actors, etc
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------

View File

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

View File

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

View File

@ -1108,14 +1108,6 @@ inline Range<const T> MakeRange(const RawBuffer<T>& from) {
return from.Data(); 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); 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); return range.length() * sizeof(T);
} }
Maybe<Range<const uint8_t>> GetRangeFromView(const dom::ArrayBufferView& view,
GLuint elemOffset,
GLuint elemCountOverride);
// - // -
template <typename T> template <typename T>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -23,24 +23,6 @@ LogModule* GetEMEVerboseLog() {
return log; 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( void CopyArrayBufferViewOrArrayBufferData(
const dom::ArrayBufferViewOrArrayBuffer& aBufferOrView, const dom::ArrayBufferViewOrArrayBuffer& aBufferOrView,
nsTArray<uint8_t>& aOutData) { nsTArray<uint8_t>& aOutData) {

View File

@ -47,36 +47,6 @@ void CopyArrayBufferViewOrArrayBufferData(
const dom::ArrayBufferViewOrArrayBuffer& aBufferOrView, const dom::ArrayBufferViewOrArrayBuffer& aBufferOrView,
nsTArray<uint8_t>& aOutData); 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); nsString KeySystemToProxyName(const nsAString& aKeySystem);
bool IsClearkeyKeySystem(const nsAString& aKeySystem); bool IsClearkeyKeySystem(const nsAString& aKeySystem);

View File

@ -35,36 +35,36 @@ nsPIDOMWindowInner* MediaKeyStatusMap::GetParentObject() const {
return mParent; 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, void MediaKeyStatusMap::Get(const ArrayBufferViewOrArrayBuffer& aKey,
OwningMediaKeyStatusOrUndefined& aOutValue, OwningMediaKeyStatusOrUndefined& aOutValue,
ErrorResult& aOutRv) const { ErrorResult& aOutRv) const {
ArrayData keyId = GetArrayBufferViewOrArrayBufferData(aKey); const KeyStatus* status = FindKey(aKey);
if (!keyId.IsValid()) { if (!status) {
aOutValue.SetUndefined(); aOutValue.SetUndefined();
return; return;
} }
for (const KeyStatus& status : mStatuses) {
if (keyId == status.mKeyId) { aOutValue.SetAsMediaKeyStatus() = status->mStatus;
aOutValue.SetAsMediaKeyStatus() = status.mStatus;
return;
}
}
aOutValue.SetUndefined();
} }
bool MediaKeyStatusMap::Has(const ArrayBufferViewOrArrayBuffer& aKey) const { bool MediaKeyStatusMap::Has(const ArrayBufferViewOrArrayBuffer& aKey) const {
ArrayData keyId = GetArrayBufferViewOrArrayBufferData(aKey); return FindKey(aKey);
if (!keyId.IsValid()) {
return false;
}
for (const KeyStatus& status : mStatuses) {
if (keyId == status.mKeyId) {
return true;
}
}
return false;
} }
uint32_t MediaKeyStatusMap::GetIterableLength() const { uint32_t MediaKeyStatusMap::GetIterableLength() const {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -659,19 +659,18 @@ already_AddRefed<Promise> AudioContext::DecodeAudioData(
} }
JSAutoRealm ar(cx, obj); 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 // Throw if the buffer is detached
aRv.ThrowTypeError("Buffer argument can't be a detached buffer"); aRv.ThrowTypeError("Buffer argument can't be a detached buffer");
return nullptr; 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. // Sniff the content of the media.
// Failed type sniffing will be handled by AsyncDecodeWebAudio. // Failed type sniffing will be handled by AsyncDecodeWebAudio.
nsAutoCString contentType; nsAutoCString contentType;

View File

@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "BiquadFilterNode.h" #include "BiquadFilterNode.h"
#include <algorithm>
#include "AlignmentUtils.h" #include "AlignmentUtils.h"
#include "AudioNodeEngine.h" #include "AudioNodeEngine.h"
#include "AudioNodeTrack.h" #include "AudioNodeTrack.h"
@ -304,46 +305,54 @@ void BiquadFilterNode::GetFrequencyResponse(const Float32Array& aFrequencyHz,
const Float32Array& aMagResponse, const Float32Array& aMagResponse,
const Float32Array& aPhaseResponse, const Float32Array& aPhaseResponse,
ErrorResult& aRv) { ErrorResult& aRv) {
aFrequencyHz.ComputeState(); UniquePtr<float[]> frequencies;
aMagResponse.ComputeState(); size_t length;
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();
}
}
const double currentTime = Context()->CurrentTime(); 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); if (length == 0) {
double q = mQ->GetValueAtTime(currentTime); return;
double gain = mGain->GetValueAtTime(currentTime); }
double detune = mDetune->GetValueAtTime(currentTime);
WebCore::Biquad biquad; frequencies = MakeUniqueForOverwriteFallible<float[]>(length);
SetParamsOnBiquad(biquad, Context()->SampleRate(), mType, freq, q, gain, if (!frequencies) {
detune); aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
biquad.getFrequencyResponse(int(length), frequencies.get(), return;
aMagResponse.Data(), aPhaseResponse.Data()); }
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 } // namespace mozilla::dom

View File

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

View File

@ -144,32 +144,30 @@ already_AddRefed<EncodedVideoChunk> EncodedVideoChunk::Constructor(
return nullptr; return nullptr;
} }
auto r = GetSharedArrayBufferData(aInit.mData); auto buffer = ProcessTypedArrays(
if (r.isErr()) { aInit.mData,
aRv.Throw(r.unwrapErr()); [&](const Span<uint8_t>& aData,
return nullptr; JS::AutoCheckCannotGC&&) -> RefPtr<MediaAlignedByteBuffer> {
} // Make sure it's in uint32_t's range.
Span<uint8_t> buf = r.unwrap(); 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. // Instead of checking *buf, size comparision is used to allow
CheckedUint32 byteLength(buf.size_bytes()); // constructing a zero-sized EncodedVideoChunk.
if (!byteLength.isValid()) { if (!buf || buf->Size() != aData.Length()) {
aRv.Throw(NS_ERROR_INVALID_ARG); aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return nullptr; return nullptr;
} }
return buf;
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;
}
RefPtr<EncodedVideoChunk> chunk(new EncodedVideoChunk( RefPtr<EncodedVideoChunk> chunk(new EncodedVideoChunk(
global, buffer.forget(), aInit.mType, aInit.mTimestamp, global, buffer.forget(), aInit.mType, aInit.mTimestamp,
@ -207,20 +205,15 @@ void EncodedVideoChunk::CopyTo(
ErrorResult& aRv) { ErrorResult& aRv) {
AssertIsOnOwningThread(); AssertIsOnOwningThread();
auto r = GetSharedArrayBufferData(aDestination); ProcessTypedArraysFixed(aDestination, [&](const Span<uint8_t>& aData) {
if (r.isErr()) { if (mBuffer->Size() > aData.size_bytes()) {
aRv.Throw(r.unwrapErr()); aRv.ThrowTypeError(
return; "Destination ArrayBuffer smaller than source EncodedVideoChunk");
} return;
Span<uint8_t> buf = r.unwrap(); }
if (mBuffer->Size() > buf.size_bytes()) { PodCopy(aData.data(), mBuffer->Data(), mBuffer->Size());
aRv.ThrowTypeError( });
"Destination ArrayBuffer smaller than source EncodedVideoChunk");
return;
}
PodCopy(buf.data(), mBuffer->Data(), mBuffer->Size());
} }
// https://w3c.github.io/webcodecs/#ref-for-deserialization-steps%E2%91%A0 // 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, MOZ_TRY_VAR(combinedLayout,
ComputeLayoutAndAllocationSize(parsedRect, format, optLayout)); 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); Maybe<uint64_t> duration = OptionalToMaybe(aInit.mDuration);
VideoColorSpaceInit colorSpace = VideoColorSpaceInit colorSpace =
PickColorSpace(OptionalToPointer(aInit.mColorSpace), aInit.mFormat); PickColorSpace(OptionalToPointer(aInit.mColorSpace), aInit.mFormat);
RefPtr<layers::Image> data; RefPtr<layers::Image> data;
MOZ_TRY_VAR(data, MOZ_TRY_VAR(
CreateImageFromBuffer(format, colorSpace, codedSize, buffer)); 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);
MOZ_ASSERT(data->GetSize() == codedSize); 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: // TODO: Spec should assign aInit.mFormat to inner format value:
// https://github.com/w3c/webcodecs/issues/509. // https://github.com/w3c/webcodecs/issues/509.
// This comment should be removed once the issue is resolved. // This comment should be removed once the issue is resolved.
@ -1660,61 +1661,56 @@ already_AddRefed<Promise> VideoFrame::CopyTo(
} }
layout = r1.unwrap(); layout = r1.unwrap();
auto r2 = GetSharedArrayBufferData(aDestination); return ProcessTypedArraysFixed(aDestination, [&](const Span<uint8_t>& aData) {
if (r2.isErr()) { if (aData.size_bytes() < layout.mAllocationSize) {
p->MaybeRejectWithTypeError("Failed to get buffer"); p->MaybeRejectWithTypeError("Destination buffer is too small");
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 p.forget(); return p.forget();
} }
pl->mOffset = l.mDestinationOffset;
pl->mStride = l.mDestinationStride;
// Copy pixels of `size` starting from `origin` on planes[i] to Sequence<PlaneLayout> planeLayouts;
// `aDestination`.
gfx::IntPoint origin( nsTArray<Format::Plane> planes = mResource->mFormat->Planes();
l.mSourceLeftBytes / mResource->mFormat->SampleBytes(planes[i]), MOZ_ASSERT(layout.mComputedLayouts.Length() == planes.Length());
l.mSourceTop);
gfx::IntSize size( // TODO: These jobs can be run in a thread pool (bug 1780656) to unblock
l.mSourceWidthBytes / mResource->mFormat->SampleBytes(planes[i]), // the current thread.
l.mSourceHeight); for (size_t i = 0; i < layout.mComputedLayouts.Length(); ++i) {
if (!mResource->CopyTo(planes[i], {origin, size}, ComputedPlaneLayout& l = layout.mComputedLayouts[i];
buffer.From(destinationOffset), uint32_t destinationOffset = l.mDestinationOffset;
static_cast<size_t>(l.mDestinationStride))) {
p->MaybeRejectWithTypeError( PlaneLayout* pl = planeLayouts.AppendElement(fallible);
nsPrintfCString("Failed to copy image data in %s plane", if (!pl) {
mResource->mFormat->PlaneName(planes[i]))); p->MaybeRejectWithTypeError("Out of memory");
return p.forget(); 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()); MOZ_ASSERT(layout.mComputedLayouts.Length() == planes.Length());
// TODO: Spec doesn't resolve with a value. See // TODO: Spec doesn't resolve with a value. See
// https://github.com/w3c/webcodecs/issues/510 This comment should be removed // https://github.com/w3c/webcodecs/issues/510 This comment should be
// once the issue is resolved. // removed once the issue is resolved.
p->MaybeResolve(planeLayouts); p->MaybeResolve(planeLayouts);
return p.forget(); return p.forget();
});
} }
// https://w3c.github.io/webcodecs/#dom-videoframe-clone // 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. * 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( static std::tuple<JS::ArrayBufferOrView, size_t, size_t> GetArrayBufferInfo(
JSContext* aCx, JSContext* aCx,
const OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer) { const OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer) {

View File

@ -73,12 +73,6 @@ Nullable<T> MaybeToNullable(const Maybe<T>& aOptional) {
* Below are helpers to operate ArrayBuffer or ArrayBufferView. * 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( Result<Ok, nsresult> CloneBuffer(
JSContext* aCx, JSContext* aCx,
OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aDest, OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aDest,

View File

@ -6,6 +6,7 @@
#include "jsapi/RTCEncodedFrameBase.h" #include "jsapi/RTCEncodedFrameBase.h"
#include "js/GCAPI.h"
#include "nsIGlobalObject.h" #include "nsIGlobalObject.h"
#include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/ScriptSettings.h"
#include "js/ArrayBuffer.h" #include "js/ArrayBuffer.h"
@ -45,9 +46,10 @@ unsigned long RTCEncodedFrameBase::Timestamp() const { return mTimestamp; }
void RTCEncodedFrameBase::SetData(const ArrayBuffer& aData) { void RTCEncodedFrameBase::SetData(const ArrayBuffer& aData) {
mData.set(aData.Obj()); mData.set(aData.Obj());
if (mFrame) { if (mFrame) {
aData.ComputeState(); aData.ProcessData([&](const Span<uint8_t>& aData, JS::AutoCheckCannotGC&&) {
mFrame->SetData(rtc::ArrayView<const uint8_t>( mFrame->SetData(
static_cast<const uint8_t*>(aData.Data()), aData.Length())); 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 * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "js/Realm.h"
#include "mozilla/dom/EventBinding.h" #include "mozilla/dom/EventBinding.h"
#include "mozilla/dom/MIDIMessageEvent.h" #include "mozilla/dom/MIDIMessageEvent.h"
#include "mozilla/dom/MIDIMessageEventBinding.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 // Set data for event. Timestamp will always be set to Now() (default for
// event) using this constructor. // event) using this constructor.
if (aEventInitDict.mData.WasPassed()) { if (aEventInitDict.mData.WasPassed()) {
const auto& a = aEventInitDict.mData.Value(); JSAutoRealm ar(aGlobal.Context(), aGlobal.Get());
a.ComputeState(); JS::Rooted<JSObject*> data(aGlobal.Context(),
e->mData = aEventInitDict.mData.Value().Obj());
Uint8Array::Create(aGlobal.Context(), owner, a.Length(), a.Data()); e->mData = JS_NewUint8ArrayFromArray(aGlobal.Context(), data);
if (NS_WARN_IF(!e->mData)) { if (NS_WARN_IF(!e->mData)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY); aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return nullptr; return nullptr;

View File

@ -198,50 +198,44 @@ already_AddRefed<Promise> WritableStreamToOutput::WriteCallback(
return nullptr; 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 // 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 // or the write didn't write it all. This avoids allocations and copies
// in common cases. // in common cases.
MOZ_ASSERT(!mPromise); MOZ_ASSERT(!mPromise);
MOZ_ASSERT(mWritten == 0); MOZ_ASSERT(mWritten == 0);
uint32_t written = 0; uint32_t written = 0;
nsresult rv = mOutput->Write(mozilla::AsChars(dataSpan).Elements(), ProcessTypedArraysFixed(data, [&](const Span<uint8_t>& aData) {
dataSpan.Length(), &written); Span<uint8_t> dataSpan = aData;
if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) { nsresult rv = mOutput->Write(mozilla::AsChars(dataSpan).Elements(),
promise->MaybeRejectWithAbortError("error writing data"); dataSpan.Length(), &written);
return promise.forget(); if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
} promise->MaybeRejectWithAbortError("error writing data");
if (NS_SUCCEEDED(rv)) { return;
if (written == dataSpan.Length()) {
promise->MaybeResolveWithUndefined();
return promise.forget();
} }
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; mPromise = promise;
nsCOMPtr<nsIEventTarget> target = mozilla::GetCurrentSerialEventTarget(); nsCOMPtr<nsIEventTarget> target = mozilla::GetCurrentSerialEventTarget();
rv = mOutput->AsyncWait(this, 0, 0, target); nsresult rv = mOutput->AsyncWait(this, 0, 0, target);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
ClearData(); ClearData();
promise->MaybeRejectWithUnknownError("error waiting to write data"); 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"); return aRv.ThrowOperationError("SyncReadFile is closed");
} }
aDestArray.ComputeState(); aDestArray.ProcessFixedData([&](const Span<uint8_t>& aData) {
auto rangeEnd = CheckedInt64(aOffset) + aData.Length();
auto rangeEnd = CheckedInt64(aOffset) + aDestArray.Length(); if (!rangeEnd.isValid()) {
if (!rangeEnd.isValid()) { return aRv.ThrowOperationError("Requested range overflows i64");
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"));
} }
if (bytesRead == 0) {
if (rangeEnd.value() > mSize) {
return aRv.ThrowOperationError( 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; } void SyncReadFile::Close() { mStream = nullptr; }

View File

@ -56,123 +56,49 @@ already_AddRefed<dom::Promise> Queue::OnSubmittedWorkDone(ErrorResult& aRv) {
return promise.forget(); 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, void Queue::WriteBuffer(const Buffer& aBuffer, uint64_t aBufferOffset,
const dom::ArrayBufferViewOrArrayBuffer& aData, const dom::ArrayBufferViewOrArrayBuffer& aData,
uint64_t aDataOffset, uint64_t aDataOffset,
const dom::Optional<uint64_t>& aSize, const dom::Optional<uint64_t>& aSize,
ErrorResult& aRv) { ErrorResult& aRv) {
uint8_t* dataContents = nullptr; dom::ProcessTypedArraysFixed(aData, [&](const Span<const uint8_t>& aData) {
uint64_t contentsSize = 0; uint64_t length = aData.Length();
GetBufferSourceDataAndSize(aData, aDataOffset, aSize, dataContents, const auto checkedSize = aSize.WasPassed()
contentsSize, "dataOffset", aRv); ? CheckedInt<size_t>(aSize.Value())
if (aRv.Failed()) { : CheckedInt<size_t>(length) - aDataOffset;
return; if (!checkedSize.isValid()) {
} aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
if (contentsSize % 4 != 0) { const auto& size = checkedSize.value();
aRv.ThrowAbortError("Byte size must be a multiple of 4"); if (aDataOffset + size > length) {
return; aRv.ThrowAbortError(nsPrintfCString("Wrong data size %" PRIuPTR, size));
} return;
}
auto alloc = if (size % 4 != 0) {
mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(contentsSize); aRv.ThrowAbortError("Byte size must be a multiple of 4");
if (alloc.isNothing()) { return;
aRv.Throw(NS_ERROR_OUT_OF_MEMORY); }
return;
}
auto handle = std::move(alloc.ref().first); auto alloc = mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(size);
auto mapping = std::move(alloc.ref().second); if (alloc.isNothing()) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
memcpy(mapping.Bytes().data(), dataContents + aDataOffset, contentsSize); auto handle = std::move(alloc.ref().first);
ipc::ByteBuf bb; auto mapping = std::move(alloc.ref().second);
ffi::wgpu_queue_write_buffer(aBuffer.mId, aBufferOffset, ToFFI(&bb));
if (!mBridge->SendQueueWriteAction(mId, mParent->mId, std::move(bb), memcpy(mapping.Bytes().data(), aData.Elements() + aDataOffset, size);
std::move(handle))) { ipc::ByteBuf bb;
MOZ_CRASH("IPC failure"); 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, void Queue::WriteTexture(const dom::GPUImageCopyTexture& aDestination,
@ -187,40 +113,39 @@ void Queue::WriteTexture(const dom::GPUImageCopyTexture& aDestination,
ffi::WGPUExtent3d extent = {}; ffi::WGPUExtent3d extent = {};
ConvertExtent3DToFFI(aSize, &extent); ConvertExtent3DToFFI(aSize, &extent);
uint8_t* dataContents = nullptr; dom::ProcessTypedArraysFixed(aData, [&](const Span<const uint8_t>& aData) {
uint64_t contentsSize = 0; if (aData.IsEmpty()) {
GetBufferSourceDataAndSize(aData, aDataLayout.mOffset, aRv.ThrowAbortError("Input size cannot be zero.");
dom::Optional<uint64_t>(), dataContents, return;
contentsSize, "dataLayout.offset", aRv); }
if (aRv.Failed()) {
return;
}
if (!contentsSize) { const auto checkedSize =
aRv.ThrowAbortError("Input size cannot be zero."); CheckedInt<size_t>(aData.Length()) - aDataLayout.mOffset;
return; if (!checkedSize.isValid()) {
} aRv.ThrowAbortError("Offset is higher than the size");
MOZ_ASSERT(dataContents != nullptr); return;
}
const auto size = checkedSize.value();
auto alloc = auto alloc = mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(size);
mozilla::ipc::UnsafeSharedMemoryHandle::CreateAndMap(contentsSize); if (alloc.isNothing()) {
if (alloc.isNothing()) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return;
return; }
}
auto handle = std::move(alloc.ref().first); auto handle = std::move(alloc.ref().first);
auto mapping = std::move(alloc.ref().second); auto mapping = std::move(alloc.ref().second);
memcpy(mapping.Bytes().data(), dataContents + aDataLayout.mOffset, memcpy(mapping.Bytes().data(), aData.Elements() + aDataLayout.mOffset,
contentsSize); size);
ipc::ByteBuf bb; ipc::ByteBuf bb;
ffi::wgpu_queue_write_texture(copyView, dataLayout, extent, ToFFI(&bb)); ffi::wgpu_queue_write_texture(copyView, dataLayout, extent, ToFFI(&bb));
if (!mBridge->SendQueueWriteAction(mId, mParent->mId, std::move(bb), if (!mBridge->SendQueueWriteAction(mId, mParent->mId, std::move(bb),
std::move(handle))) { std::move(handle))) {
MOZ_CRASH("IPC failure"); MOZ_CRASH("IPC failure");
} }
});
} }
static WebGLTexelFormat ToWebGLTexelFormat(gfx::SurfaceFormat aFormat) { static WebGLTexelFormat ToWebGLTexelFormat(gfx::SurfaceFormat aFormat) {

View File

@ -2389,7 +2389,7 @@ void WebSocket::Send(const ArrayBuffer& aData, ErrorResult& aRv) {
AssertIsOnTargetThread(); AssertIsOnTargetThread();
static_assert( 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"); "byte-sized data required");
nsCString msgString; nsCString msgString;
@ -2404,7 +2404,7 @@ void WebSocket::Send(const ArrayBufferView& aData, ErrorResult& aRv) {
AssertIsOnTargetThread(); AssertIsOnTargetThread();
static_assert( 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"); "byte-sized data required");
nsCString msgString; nsCString msgString;

View File

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

View File

@ -139,9 +139,7 @@ class ScaffoldingConverter<RustBuffer> {
return Err("Bad argument type"_ns); return Err("Bad argument type"_ns);
} }
const dom::ArrayBuffer& arrayBuf = aValue.GetAsArrayBuffer(); return OwnedRustBuffer::FromArrayBuffer(aValue.GetAsArrayBuffer());
arrayBuf.ComputeState();
return OwnedRustBuffer::FromArrayBuffer(arrayBuf);
} }
static RustBuffer IntoRust(OwnedRustBuffer&& aValue) { 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, MOZ_LOG(sUniFFIPointerLogger, LogLevel::Info,
("[UniFFI] Writing Pointer to buffer")); ("[UniFFI] Writing Pointer to buffer"));
aArrayBuff.ComputeState();
CheckedUint32 position = aPosition; aArrayBuff.ProcessData([&](const Span<uint8_t>& aData,
CheckedUint32 end = position + 8; JS::AutoCheckCannotGC&&) {
if (!end.isValid() || end.value() > aArrayBuff.Length()) { CheckedUint32 end = aPosition + 8;
aError.ThrowRangeError("position is out of range"); if (!end.isValid() || end.value() > aData.Length()) {
return; 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 // in Rust and Read(), a u64 is read as BigEndian and then converted to
uint8_t* data_ptr = aArrayBuff.Data() + // a pointer we do the reverse here
aPosition; // Pointer arithmetic, move by position bytes const auto& data_ptr = aData.Subspan(aPosition, 8);
mozilla::BigEndian::writeUint64(data_ptr, (uint64_t)GetPtr()); mozilla::BigEndian::writeUint64(data_ptr.Elements(), (uint64_t)GetPtr());
});
} }
UniFFIPointer::UniFFIPointer(void* aPtr, const UniFFIPointerType* aType) { UniFFIPointer::UniFFIPointer(void* aPtr, const UniFFIPointerType* aType) {