Backed out 13 changesets (bug 1690111) for causing fetch related crashes.

Backed out changeset 5f2c25d194ad (bug 1690111)
Backed out changeset 76c408bcd053 (bug 1690111)
Backed out changeset 6d0649fdafff (bug 1690111)
Backed out changeset c1330b5e8c43 (bug 1690111)
Backed out changeset 5fa36d8fd2a5 (bug 1690111)
Backed out changeset daf7d747853a (bug 1690111)
Backed out changeset f70e09a7f5c6 (bug 1690111)
Backed out changeset 40c6d6eed7f8 (bug 1690111)
Backed out changeset 692f2a759573 (bug 1690111)
Backed out changeset 7140866dd9f6 (bug 1690111)
Backed out changeset 2865fe682139 (bug 1690111)
Backed out changeset 9dcd2416f8a5 (bug 1690111)
Backed out changeset 9c411bf84079 (bug 1690111)
This commit is contained in:
Cosmin Sabou 2023-09-11 17:55:24 +03:00
parent 48be8d1388
commit 680b4364c7
102 changed files with 1688 additions and 2846 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -172,26 +172,17 @@ void nsDOMDataChannel::Close() {
// All of the following is copy/pasted from WebSocket.cpp.
void nsDOMDataChannel::Send(const nsAString& aData, ErrorResult& aRv) {
if (!CheckReadyState(aRv)) {
return;
}
nsAutoCString msgString;
if (!AppendUTF16toUTF8(aData, msgString, mozilla::fallible_t())) {
aRv.Throw(NS_ERROR_FILE_TOO_BIG);
return;
}
mDataChannel->SendMsg(msgString, aRv);
Send(nullptr, &msgString, false, aRv);
}
void nsDOMDataChannel::Send(Blob& aData, ErrorResult& aRv) {
MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
if (!CheckReadyState(aRv)) {
return;
}
nsCOMPtr<nsIInputStream> msgStream;
aData.CreateInputStream(getter_AddRefs(msgStream), aRv);
if (NS_WARN_IF(aRv.Failed())) {
@ -208,42 +199,50 @@ void nsDOMDataChannel::Send(Blob& aData, ErrorResult& aRv) {
return;
}
mDataChannel->SendBinaryBlob(aData, aRv);
Send(&aData, nullptr, true, aRv);
}
void nsDOMDataChannel::Send(const ArrayBuffer& aData, ErrorResult& aRv) {
MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
if (!CheckReadyState(aRv)) {
return;
}
aData.ComputeState();
nsCString msgString;
if (!aData.AppendDataTo(msgString)) {
static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
uint32_t len = aData.Length();
char* data = reinterpret_cast<char*>(aData.Data());
nsDependentCSubstring msgString;
if (!msgString.Assign(data, len, mozilla::fallible_t())) {
aRv.Throw(NS_ERROR_FILE_TOO_BIG);
return;
}
mDataChannel->SendBinaryMsg(msgString, aRv);
Send(nullptr, &msgString, true, aRv);
}
void nsDOMDataChannel::Send(const ArrayBufferView& aData, ErrorResult& aRv) {
MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
if (!CheckReadyState(aRv)) {
return;
}
aData.ComputeState();
nsCString msgString;
if (!aData.AppendDataTo(msgString)) {
static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
uint32_t len = aData.Length();
char* data = reinterpret_cast<char*>(aData.Data());
nsDependentCSubstring msgString;
if (!msgString.Assign(data, len, mozilla::fallible_t())) {
aRv.Throw(NS_ERROR_FILE_TOO_BIG);
return;
}
mDataChannel->SendBinaryMsg(msgString, aRv);
Send(nullptr, &msgString, true, aRv);
}
bool nsDOMDataChannel::CheckReadyState(ErrorResult& aRv) {
void nsDOMDataChannel::Send(mozilla::dom::Blob* aMsgBlob,
const nsACString* aMsgString, bool aIsBinary,
mozilla::ErrorResult& aRv) {
MOZ_ASSERT(NS_IsMainThread());
uint16_t state = mozilla::DataChannel::CLOSED;
if (!mSentClose) {
@ -254,18 +253,26 @@ bool nsDOMDataChannel::CheckReadyState(ErrorResult& aRv) {
// look like WebSockets
if (state == mozilla::DataChannel::CONNECTING) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return false;
return;
}
if (state == mozilla::DataChannel::CLOSING ||
state == mozilla::DataChannel::CLOSED) {
return false;
return;
}
MOZ_ASSERT(state == mozilla::DataChannel::OPEN,
"Unknown state in nsDOMDataChannel::Send");
return true;
if (aMsgBlob) {
mDataChannel->SendBinaryBlob(*aMsgBlob, aRv);
} else {
if (aIsBinary) {
mDataChannel->SendBinaryMsg(*aMsgString, aRv);
} else {
mDataChannel->SendMsg(*aMsgString, aRv);
}
}
}
nsresult nsDOMDataChannel::DoOnMessageAvailable(const nsACString& aData,

View File

@ -108,7 +108,8 @@ class nsDOMDataChannel final : public mozilla::DOMEventTargetHelper,
~nsDOMDataChannel();
private:
bool CheckReadyState(mozilla::ErrorResult& aRv);
void Send(mozilla::dom::Blob* aMsgBlob, const nsACString* aMsgString,
bool aIsBinary, mozilla::ErrorResult& aRv);
void ReleaseSelf();

View File

@ -71,32 +71,31 @@ struct DictionaryBase {
bool IsAnyMemberPresent() const { return mIsAnyMemberPresent; }
};
template <class T>
constexpr bool is_dom_dictionary = std::is_base_of_v<DictionaryBase, T>;
template <typename T>
inline std::enable_if_t<is_dom_dictionary<T>, void> ImplCycleCollectionUnlink(
T& aDictionary) {
inline std::enable_if_t<std::is_base_of<DictionaryBase, T>::value, void>
ImplCycleCollectionUnlink(T& aDictionary) {
aDictionary.UnlinkForCC();
}
template <typename T>
inline std::enable_if_t<is_dom_dictionary<T>, void> ImplCycleCollectionTraverse(
nsCycleCollectionTraversalCallback& aCallback, T& aDictionary,
const char* aName, uint32_t aFlags = 0) {
inline std::enable_if_t<std::is_base_of<DictionaryBase, T>::value, void>
ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
T& aDictionary, const char* aName,
uint32_t aFlags = 0) {
aDictionary.TraverseForCC(aCallback, aFlags);
}
template <typename T>
inline std::enable_if_t<is_dom_dictionary<T>, void> ImplCycleCollectionUnlink(
UniquePtr<T>& aDictionary) {
inline std::enable_if_t<std::is_base_of<DictionaryBase, T>::value, void>
ImplCycleCollectionUnlink(UniquePtr<T>& aDictionary) {
aDictionary.reset();
}
template <typename T>
inline std::enable_if_t<is_dom_dictionary<T>, void> ImplCycleCollectionTraverse(
nsCycleCollectionTraversalCallback& aCallback, UniquePtr<T>& aDictionary,
const char* aName, uint32_t aFlags = 0) {
inline std::enable_if_t<std::is_base_of<DictionaryBase, T>::value, void>
ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
UniquePtr<T>& aDictionary, const char* aName,
uint32_t aFlags = 0) {
if (aDictionary) {
ImplCycleCollectionTraverse(aCallback, *aDictionary, aName, aFlags);
}
@ -106,30 +105,10 @@ inline std::enable_if_t<is_dom_dictionary<T>, void> ImplCycleCollectionTraverse(
// detect typed array/buffer/view template arguments.
struct AllTypedArraysBase {};
template <class T>
constexpr bool is_dom_typed_array = std::is_base_of_v<AllTypedArraysBase, T>;
// Struct that serves as a base class for all unions.
// Particularly useful so we can use std::is_base_of to detect union
// template arguments.
struct AllUnionBase {};
template <class T>
constexpr bool is_dom_union = std::is_base_of_v<AllUnionBase, T>;
// Struct that serves as a base class for all owning unions.
// Particularly useful so we can use std::is_base_of to detect owning union
// template arguments.
struct AllOwningUnionBase : public AllUnionBase {};
template <class T>
constexpr bool is_dom_owning_union = std::is_base_of_v<AllOwningUnionBase, T>;
struct UnionWithTypedArraysBase {};
template <class T>
constexpr bool is_dom_union_with_typedarray_members =
std::is_base_of_v<UnionWithTypedArraysBase, T>;
struct AllOwningUnionBase {};
struct EnumEntry {
const char* value;

View File

@ -1969,9 +1969,10 @@ void DoTraceSequence(JSTracer* trc, nsTArray<T>& seq);
// Class used to trace sequences, with specializations for various
// sequence types.
template <typename T, bool isDictionary = is_dom_dictionary<T>,
bool isTypedArray = is_dom_typed_array<T>,
bool isOwningUnion = is_dom_owning_union<T>>
template <typename T,
bool isDictionary = std::is_base_of<DictionaryBase, T>::value,
bool isTypedArray = std::is_base_of<AllTypedArraysBase, T>::value,
bool isOwningUnion = std::is_base_of<AllOwningUnionBase, T>::value>
class SequenceTracer {
explicit SequenceTracer() = delete; // Should never be instantiated
};

View File

@ -13469,6 +13469,7 @@ class CGUnionStruct(CGThing):
enumValuesNoUninit = [x for x in enumValues if x != "eUninitialized"]
bases = [ClassBase("AllOwningUnionBase")] if self.ownsMembers else []
enums = [
ClassGroup(
[
@ -13483,37 +13484,9 @@ class CGUnionStruct(CGThing):
]
)
]
bases = [
ClassBase("AllOwningUnionBase" if self.ownsMembers else "AllUnionBase")
]
typeAliases = []
bufferSourceTypes = [
t.name for t in self.type.flatMemberTypes if t.isBufferSource()
]
if len(bufferSourceTypes) > 0:
bases.append(ClassBase("UnionWithTypedArraysBase"))
memberTypesCount = len(self.type.flatMemberTypes)
if self.type.hasNullableType:
memberTypesCount += 1
typeAliases = [
ClassUsingDeclaration(
"ApplyToTypedArrays",
"binding_detail::ApplyToTypedArraysHelper<%s, %s, %s>"
% (
selfName,
toStringBool(memberTypesCount > len(bufferSourceTypes)),
", ".join(bufferSourceTypes),
),
)
]
return CGClass(
selfName,
bases=bases,
typeAliases=typeAliases,
members=members,
constructors=constructors,
methods=methods,
@ -13728,29 +13701,6 @@ class ClassMethod(ClassItem):
class ClassUsingDeclaration(ClassItem):
"""
Used for declaring an alias for a type in a CGClass
name is the name of the alias
type is the type to declare an alias of
visibility determines the visibility of the alias (public,
protected, private), defaults to public.
"""
def __init__(self, name, type, visibility="public"):
self.type = type
ClassItem.__init__(self, name, visibility)
def declare(self, cgClass):
return "using %s = %s;\n\n" % (self.name, self.type)
def define(self, cgClass):
return ""
class ClassUsingFromBaseDeclaration(ClassItem):
"""
Used for importing a name from a base class into a CGClass
@ -14063,7 +14013,6 @@ class CGClass(CGThing):
self,
name,
bases=[],
typeAliases=[],
members=[],
constructors=[],
destructor=None,
@ -14082,7 +14031,6 @@ class CGClass(CGThing):
CGThing.__init__(self)
self.name = name
self.bases = bases
self.typeAliases = typeAliases
self.members = members
self.constructors = constructors
# We store our single destructor in a list, since all of our
@ -14198,7 +14146,6 @@ class CGClass(CGThing):
disallowedCopyConstructors = []
order = [
self.typeAliases,
self.enums,
self.unions,
self.members,
@ -16542,9 +16489,7 @@ class CGDOMJSProxyHandler(CGClass):
methods = [
CGDOMJSProxyHandler_getOwnPropDescriptor(descriptor),
CGDOMJSProxyHandler_defineProperty(descriptor),
ClassUsingFromBaseDeclaration(
"mozilla::dom::DOMProxyHandler", "defineProperty"
),
ClassUsingDeclaration("mozilla::dom::DOMProxyHandler", "defineProperty"),
CGDOMJSProxyHandler_ownPropNames(descriptor),
CGDOMJSProxyHandler_hasOwn(descriptor),
CGDOMJSProxyHandler_get(descriptor),
@ -16586,7 +16531,7 @@ class CGDOMJSProxyHandler(CGClass):
CGDOMJSProxyHandler_definePropertySameOrigin(descriptor),
CGDOMJSProxyHandler_set(descriptor),
CGDOMJSProxyHandler_EnsureHolder(descriptor),
ClassUsingFromBaseDeclaration(
ClassUsingDeclaration(
"MaybeCrossOriginObjectMixins", "EnsureHolder"
),
]
@ -20746,7 +20691,7 @@ class CGJSImplClass(CGBindingImplClass):
override=True,
body=body,
),
ClassUsingFromBaseDeclaration(parentClass, methodName),
ClassUsingDeclaration(parentClass, methodName),
]

View File

@ -282,8 +282,9 @@ template <typename T>
// Accept WebIDL dictionaries
template <class T>
[[nodiscard]] std::enable_if_t<is_dom_dictionary<T>, bool> ToJSValue(
JSContext* aCx, const T& aArgument, JS::MutableHandle<JS::Value> aValue) {
[[nodiscard]] std::enable_if_t<std::is_base_of<DictionaryBase, T>::value, bool>
ToJSValue(JSContext* aCx, const T& aArgument,
JS::MutableHandle<JS::Value> aValue) {
return aArgument.ToObjectInternal(aCx, aValue);
}
@ -339,8 +340,10 @@ template <class T>
// Accept owning WebIDL unions.
template <typename T>
[[nodiscard]] std::enable_if_t<is_dom_owning_union<T>, bool> ToJSValue(
JSContext* aCx, const T& aArgument, JS::MutableHandle<JS::Value> aValue) {
[[nodiscard]] std::enable_if_t<std::is_base_of<AllOwningUnionBase, T>::value,
bool>
ToJSValue(JSContext* aCx, const T& aArgument,
JS::MutableHandle<JS::Value> aValue) {
JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
return aArgument.ToJSVal(aCx, global, aValue);
}

View File

@ -7,8 +7,6 @@
#ifndef mozilla_dom_TypedArray_h
#define mozilla_dom_TypedArray_h
#include <string>
#include <type_traits>
#include <utility>
#include "js/ArrayBuffer.h"
@ -19,12 +17,7 @@
#include "js/ScalarType.h" // JS::Scalar::Type
#include "js/SharedArrayBuffer.h"
#include "mozilla/Attributes.h"
#include "mozilla/Buffer.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/Result.h"
#include "mozilla/Vector.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/SpiderMonkeyInterface.h"
#include "nsWrapperCache.h"
#include "nsWrapperCacheInlines.h"
@ -36,304 +29,7 @@ namespace mozilla::dom {
* that has a way of initializing a TypedArray from an existing typed array, and
* a subclass of the base class that supports creation of a relevant typed array
* or array buffer object.
*
* Accessing the data of a TypedArray is tricky. The underlying storage is in a
* JS object, which is subject to the JS GC. The memory for the array can be
* either inline in the JSObject or stored separately. If the data is stored
* inline then the exact location in memory of the data buffer can change as a
* result of a JS GC (which is a moving GC). Running JS code can also mutate the
* data (including the length). For all these reasons one has to be careful when
* holding a pointer to the data or keeping a local copy of the length value. On
* the other hand, most code that takes a TypedArray has to access its data at
* some point, to process it in some way. The TypedArray class tries to supply a
* number of helper APIs, so that the most common cases of processing the data
* can be done safely, without having to check the caller very closely for
* potential security issues. The main classes of processing TypedArray data
* are:
*
* 1) Appending a copy of the data (or of a subset of the data) to a different
* data structure
* 2) Copying the data (or a subset of the data) into a different data
* structure
* 3) Creating a new data structure with a copy of the data (or of a subset of
* the data)
* 4) Processing the data in some other way
*
* The APIs for the first 3 classes all return a boolean and take an optional
* argument named aCalculator. aCalculator should be a lambda taking a size_t
* argument which will be passed the total length of the data in the typed
* array. aCalculator is allowed to return a std::pair<size_t, size_t> or a
* Maybe<std::pair<size_t, size_t>>. The return value should contain the offset
* and the length of the subset of the data that should be appended, copied or
* used for creating a new datastructure. If the calculation can fail then
* aCalculator should return a Maybe<std::pair<size_t, size_t>>, with Nothing()
* signaling that the operation should be aborted.
* The return value of these APIs will be false if appending, copying or
* creating a structure with the new data failed, or if the optional aCalculator
* lambda returned Nothing().
*
* Here are the different APIs:
*
* 1) Appending to a different data structure
*
* There are AppendDataTo helpers for nsCString, nsTArray<T>,
* FallibleTArray<T> and Vector<T>. The signatures are:
*
* template <typename... Calculator>
* [[nodiscard]] bool AppendDataTo(nsCString& aResult, Calculator&&...
* aCalculator) const;
*
* template <typename T, typename... Calculator>
* [[nodiscard]] bool AppendDataTo(nsTArray<T>& aResult, Calculator&&...
* aCalculator) const;
*
* template <typename T, typename... Calculator>
* [[nodiscard]] bool AppendDataTo(FallibleTArray<T>& aResult,
* Calculator&&... aCalculator) const;
*
* template <typename T, typename... Calculator>
* [[nodiscard]] bool AppendDataTo(Vector<T>& aResult, Calculator&&...
* aCalculator) const;
*
* The data (or the calculated subset) will be appended to aResult by using
* the appropriate fallible API. If the append fails then AppendDataTo will
* return false. The aCalculator optional argument is described above.
*
* Examples:
*
* Vector<uint32_t> array;
* if (!aUint32Array.AppendDataTo(array)) {
* aError.ThrowTypeError("Failed to copy data from typed array");
* return;
* }
*
* size_t offset, length;
* // Getting offset and length values from somewhere.
* FallibleTArray<float> array;
* if (!aFloat32Array.AppendDataTo(array, [&](const size_t& aLength) {
* size_t dataLength = std::min(aLength - offset, length);
* return std::make_pair(offset, dataLength);
* }) {
* aError.ThrowTypeError("Failed to copy data from typed array");
* return;
* }
*
* size_t offset, length;
* // Getting offset and length values from somewhere.
* FallibleTArray<float> array;
* if (!aFloat32Array.AppendDataTo(array, [&](const size_t& aLength) {
* if (aLength < offset + length) {
* return Maybe<std::pair<size_t, size_t>>();
* }
* size_t dataLength = std::min(aLength - offset, length);
* return Some(std::make_pair(offset, dataLength));
* })) {
* aError.ThrowTypeError("Failed to copy data from typed array");
* return;
* }
*
*
* 2) Copying into a different data structure
*
* There is a CopyDataTo helper for a fixed-size buffer. The signature is:
*
* template <typename T, size_t N, typename... Calculator>
* [[nodiscard]] bool CopyDataTo(T (&aResult)[N],
* Calculator&&... aCalculator) const;
*
* The data (or the calculated subset) will be copied to aResult, starting
* at aResult[0]. If the length of the data to be copied is bigger than the
* size of the fixed-size buffer (N) then nothing will be copied and
* CopyDataTo will return false. The aCalculator optional argument is
* described above.
*
* Examples:
*
* float data[3];
* if (!aFloat32Array.CopyDataTo(data)) {
* aError.ThrowTypeError("Typed array doesn't contain the right amount"
* "of data");
* return;
* }
*
* size_t offset;
* // Getting offset value from somewhere.
* uint32_t data[3];
* if (!aUint32Array.CopyDataTo(data, [&](const size_t& aLength) {
* if (aLength - offset != ArrayLength(data)) {
* aError.ThrowTypeError("Typed array doesn't contain the right"
* " amount of data");
* return Maybe<std::pair<size_t, size_t>>();
* }
* return Some(std::make_pair(offset, ArrayLength(data)));
* }) {
* return;
* }
*
* 3) Creating a new data structure with a copy of the data (or a subset of the
* data)
*
* There are CreateFromData helper for creating a Vector<element_type>, a
* UniquePtr<element_type[]> and a Buffer<element_type>.
*
* template <typename... Calculator>
* [[nodiscard]] Maybe<Vector<element_type>>
* CreateFromData<Vector<element_type>>(
* Calculator&&... aCalculator) const;
*
* template <typename... Calculator>
* [[nodiscard]] Maybe<UniquePtr<element_type[]>>
* CreateFromData<UniquePtr<element_type[]>>(
* Calculator&&... aCalculator) const;
*
* template <typename... Calculator>
* [[nodiscard]] Maybe<Buffer<element_type>>
* CreateFromData<Buffer<element_type>>(
* Calculator&&... aCalculator) const;
*
* A new container will be created, and the data (or the calculated subset)
* will be copied to it. The container will be returned inside a Maybe<>.
* If creating the container with the right size fails then Nothing() will
* be returned. The aCalculator optional argument is described above.
*
* Examples:
*
* Maybe<Buffer<uint8_t>> buffer =
* aUint8Array.CreateFromData<Buffer<uint8_t>>();
* if (buffer.isNothing()) {
* aError.ThrowTypeError("Failed to create a buffer");
* return;
* }
*
* size_t offset, length;
* // Getting offset and length values from somewhere.
* Maybe<Buffer<uint8_t>> buffer =
* aUint8Array.CreateFromData<Buffer<uint8_t>>([&](
* const size_t& aLength) {
* if (aLength - offset != ArrayLength(data)) {
* aError.ThrowTypeError(
* "Typed array doesn't contain the right amount" of data");
* return Maybe<std::pair<size_t, size_t>>();
* }
* return Some(std::make_pair(offset, ArrayLength(data)));
* });
* if (buffer.isNothing()) {
* return;
* }
*
* 4) Processing the data in some other way
*
* This is the API for when none of the APIs above are appropriate for your
* usecase. As these are the most dangerous APIs you really should check
* first if you can't use one of the safer alternatives above. The reason
* these APIs are more dangerous is because they give access to the typed
* array's data directly, and the location of that data can be changed by
* the JS GC (due to generational and/or compacting collection). There are
* two APIs for processing data:
*
* template <typename Processor>
* [[nodiscard]] ProcessReturnType<Processor> ProcessData(
* Processor&& aProcessor) const;
*
* template <typename Processor>
* [[nodiscard]] ProcessReturnType<Processor> ProcessFixedData(
* Processor&& aProcessor) const;
*
* ProcessData will call the lambda with as arguments a |const Span<>&|
* wrapping the data pointer and length for the data in the typed array, and
* a |JS::AutoCheckCannotGC&&|. The lambda will execute in a context where
* GC is not allowed.
*
* ProcessFixedData will call the lambda with as argument |const Span<>&|.
* For small typed arrays where the data is stored inline in the typed
* array, and thus could move during GC, then the data will be copied into a
* fresh out-of-line allocation before the lambda is called.
*
* The signature of the lambdas for ProcessData and ProcessFixedData differ
* in that the ProcessData lambda will additionally be passed a nogc token
* to prevent GC from occurring and invalidating the data. If the processing
* you need to do in the lambda can't be proven to not GC then you should
* probably use ProcessFixedData instead. There are cases where you need to
* do something that can cause a GC but you don't actually need to access
* the data anymore. A good example would be throwing a JS exception and
* return. For those very specific cases you can call nogc.reset() before
* doing anything that causes a GC. Be extra careful to not access the data
* after you called nogc.reset().
*
* Extra care must be taken to not let the Span<> or any pointers derived
* from it escape the lambda, as the position in memory of the typed array's
* data can change again once we leave the lambda (invalidating the
* pointers). The lambda passed to ProcessData is not allowed to do anything
* that will trigger a GC, and the GC rooting hazard analysis will enforce
* that.
*
* Both ProcessData and ProcessFixedData will pin the typed array's length
* while calling the lambda, to block any changes to the length of the data.
* Note that this means that the lambda itself isn't allowed to change the
* length of the typed array's data. Any attempt to change the length will
* throw a JS exception.
*
* The return type of ProcessData and ProcessFixedData depends on the return
* type of the lambda, as they forward the return value from the lambda to
* the caller of ProcessData or ProcessFixedData.
*
* Examples:
*
* aUint32Array.ProcessData([] (const Span<uint32_t>& aData,
* JS::AutoCheckCannotGC&&) {
* for (size_t i = 0; i < aData.Length(); ++i) {
* aData[i] = i;
* }
* });
*
* aUint32Array.ProcessData([&] (const Span<uint32_t>& aData,
* JS::AutoCheckCannotGC&& nogc) {
* for (size_t i = 0; i < aData.Length(); ++i) {
* if (!aData[i]) {
* nogc.reset();
* ThrowJSException("Data shouldn't contain 0");
* return;
* };
* DoSomething(aData[i]);
* }
* });
*
* uint8_t max = aUint8Array.ProcessData([] (const Span<uint8_t>& aData) {
* return std::max_element(aData.cbegin(), aData.cend());
* });
*
* aUint8Array.ProcessFixedData([] (const Span<uint8_t>& aData) {
* return CallFunctionThatMightGC(aData);
* });
*
*
* In addition to the above APIs we provide helpers to call them on the typed
* array members of WebIDL unions. We have helpers for the 4 different sets of
* APIs above. The return value of the helpers depends on whether the union can
* contain a type other than a typed array. If the union can't contain a type
* other than a typed array then the return type is simply the type returned by
* the corresponding API above. If the union can contain a type other than a
* typed array then the return type of the helper is a Maybe<> wrapping the
* actual return type, with Nothing() signifying that the union contained a
* non-typed array value.
*
* template <typename ToType, typename T>
* [[nodiscard]] auto AppendTypedArrayDataTo(const T& aUnion,
* ToType& aResult);
*
* template <typename ToType, typename T>
* [[nodiscard]] auto CreateFromTypedArrayData(const T& aUnion);
*
* template <typename T, typename Processor>
* [[nodiscard]] auto ProcessTypedArrays(
* const T& aUnion, Processor&& aProcessor);
*
* template <typename T, typename Processor>
* [[nodiscard]] auto ProcessTypedArraysFixed(const T& aUnion,
* Processor&& aProcessor);
*
*/
template <class ArrayT>
struct TypedArray_base : public SpiderMonkeyInterfaceObjectStorage,
AllTypedArraysBase {
@ -348,10 +44,7 @@ struct TypedArray_base : public SpiderMonkeyInterfaceObjectStorage,
mLength(aOther.mLength),
mShared(aOther.mShared),
mComputed(aOther.mComputed) {
aOther.mData = nullptr;
aOther.mLength = 0;
aOther.mShared = false;
aOther.mComputed = false;
aOther.Reset();
}
private:
@ -370,7 +63,21 @@ struct TypedArray_base : public SpiderMonkeyInterfaceObjectStorage,
// About shared memory:
//
// Any DOM TypedArray as well as any DOM ArrayBufferView can map the
// memory of either a JS ArrayBuffer or a JS SharedArrayBuffer.
// memory of either a JS ArrayBuffer or a JS SharedArrayBuffer. If
// the TypedArray maps a SharedArrayBuffer the Length() and Data()
// accessors on the DOM view will return zero and nullptr; to get
// the actual length and data, call the LengthAllowShared() and
// DataAllowShared() accessors instead.
//
// Two methods are available for determining if a DOM view maps
// shared memory. The IsShared() method is cheap and can be called
// if the view has been computed; the JS_GetTypedArraySharedness()
// method is slightly more expensive and can be called on the Obj()
// value if the view may not have been computed and if the value is
// known to represent a JS TypedArray.
//
// (Just use JS::IsSharedArrayBuffer() to test if any object is of
// that type.)
//
// Code that elects to allow views that map shared memory to be used
// -- ie, code that "opts in to shared memory" -- should generally
@ -394,310 +101,57 @@ struct TypedArray_base : public SpiderMonkeyInterfaceObjectStorage,
// Finally, note that the buffer memory of a SharedArrayBuffer is
// not detachable.
public:
/**
* Helper functions to append a copy of this typed array's data to a
* container. Returns false if the allocation for copying the data fails.
*
* aCalculator is an optional argument to which one can pass a lambda
* expression that will calculate the offset and length of the data to copy
* out of the typed array. aCalculator will be called with one argument of
* type size_t set to the length of the data in the typed array. It is allowed
* to return a std::pair<size_t, size_t> or a Maybe<std::pair<size_t, size_t>>
* containing the offset and the length of the subset of the data that we
* should copy. If the calculation can fail then aCalculator should return a
* Maybe<std::pair<size_t, size_t>>, if .isNothing() returns true for the
* return value then AppendDataTo will return false and the data won't be
* copied.
*/
template <typename... Calculator>
[[nodiscard]] bool AppendDataTo(nsCString& aResult,
Calculator&&... aCalculator) const {
static_assert(sizeof...(aCalculator) <= 1,
"AppendDataTo takes at most one aCalculator");
return ProcessDataHelper(
[&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) {
return aResult.Append(aData, fallible);
},
std::forward<Calculator>(aCalculator)...);
inline bool IsShared() const {
MOZ_ASSERT(mComputed);
return mShared;
}
template <typename T, typename... Calculator>
[[nodiscard]] bool AppendDataTo(nsTArray<T>& aResult,
Calculator&&... aCalculator) const {
static_assert(sizeof...(aCalculator) <= 1,
"AppendDataTo takes at most one aCalculator");
return ProcessDataHelper(
[&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) {
return aResult.AppendElements(aData, fallible);
},
std::forward<Calculator>(aCalculator)...);
inline element_type* Data() const {
MOZ_ASSERT(mComputed);
return mData;
}
template <typename T, typename... Calculator>
[[nodiscard]] bool AppendDataTo(FallibleTArray<T>& aResult,
Calculator&&... aCalculator) const {
static_assert(sizeof...(aCalculator) <= 1,
"AppendDataTo takes at most one aCalculator");
return ProcessDataHelper(
[&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) {
return aResult.AppendElements(aData, fallible);
},
std::forward<Calculator>(aCalculator)...);
// 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);
}
template <typename T, typename... Calculator>
[[nodiscard]] bool AppendDataTo(Vector<T>& aResult,
Calculator&&... aCalculator) const {
static_assert(sizeof...(aCalculator) <= 1,
"AppendDataTo takes at most one aCalculator");
return ProcessDataHelper(
[&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) {
return aResult.append(aData.Elements(), aData.Length());
},
std::forward<Calculator>(aCalculator)...);
inline uint32_t Length() const {
MOZ_ASSERT(mComputed);
return mLength;
}
/**
* Helper functions to copy this typed array's data to a container. This will
* clear any existing data in the container.
*
* See the comments for AppendDataTo for information on the aCalculator
* argument.
*/
template <typename T, size_t N, typename... Calculator>
[[nodiscard]] bool CopyDataTo(T (&aResult)[N],
Calculator&&... aCalculator) const {
static_assert(sizeof...(aCalculator) <= 1,
"CopyDataTo takes at most one aCalculator");
return ProcessDataHelper(
[&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) {
if (aData.Length() != N) {
return false;
}
for (size_t i = 0; i < N; ++i) {
aResult[i] = aData[i];
}
return true;
},
std::forward<Calculator>(aCalculator)...);
}
/**
* Helper functions to copy this typed array's data to a newly created
* container. Returns Nothing() if creating the container with the right size
* fails.
*
* See the comments for AppendDataTo for information on the aCalculator
* argument.
*/
template <typename T, typename... Calculator,
typename IsVector =
std::enable_if_t<std::is_same_v<Vector<element_type>, T>>>
[[nodiscard]] Maybe<Vector<element_type>> CreateFromData(
Calculator&&... aCalculator) const {
static_assert(sizeof...(aCalculator) <= 1,
"CreateFromData takes at most one aCalculator");
return CreateFromDataHelper<T>(
[&](const Span<const element_type>& aData,
Vector<element_type>& aResult) {
if (!aResult.initCapacity(aData.Length())) {
return false;
}
aResult.infallibleAppend(aData.Elements(), aData.Length());
return true;
},
std::forward<Calculator>(aCalculator)...);
}
template <typename T, typename... Calculator,
typename IsUniquePtr =
std::enable_if_t<std::is_same_v<T, UniquePtr<element_type[]>>>>
[[nodiscard]] Maybe<UniquePtr<element_type[]>> CreateFromData(
Calculator&&... aCalculator) const {
static_assert(sizeof...(aCalculator) <= 1,
"CreateFromData takes at most one aCalculator");
return CreateFromDataHelper<T>(
[&](const Span<const element_type>& aData,
UniquePtr<element_type[]>& aResult) {
aResult =
MakeUniqueForOverwriteFallible<element_type[]>(aData.Length());
if (!aResult.get()) {
return false;
}
memcpy(aResult.get(), aData.Elements(), aData.size_bytes());
return true;
},
std::forward<Calculator>(aCalculator)...);
}
template <typename T, typename... Calculator,
typename IsBuffer =
std::enable_if_t<std::is_same_v<T, Buffer<element_type>>>>
[[nodiscard]] Maybe<Buffer<element_type>> CreateFromData(
Calculator&&... aCalculator) const {
static_assert(sizeof...(aCalculator) <= 1,
"CreateFromData takes at most one aCalculator");
return CreateFromDataHelper<T>(
[&](const Span<const element_type>& aData,
Buffer<element_type>& aResult) {
Maybe<Buffer<element_type>> buffer =
Buffer<element_type>::CopyFrom(aData);
if (buffer.isNothing()) {
return false;
}
aResult = buffer.extract();
return true;
},
std::forward<Calculator>(aCalculator)...);
}
private:
template <typename Processor, typename R = decltype(std::declval<Processor>()(
std::declval<Span<element_type>>(),
std::declval<JS::AutoCheckCannotGC>()))>
using ProcessNoGCReturnType = R;
template <typename Processor>
[[nodiscard]] static inline ProcessNoGCReturnType<Processor>
CallProcessorNoGC(const Span<element_type>& aData, Processor&& aProcessor,
JS::AutoCheckCannotGC&& nogc) {
MOZ_ASSERT(
aData.IsEmpty() || aData.Elements(),
"We expect a non-null data pointer for typed arrays that aren't empty");
return aProcessor(aData, std::move(nogc));
}
template <typename Processor, typename R = decltype(std::declval<Processor>()(
std::declval<Span<element_type>>()))>
using ProcessReturnType = R;
template <typename Processor>
[[nodiscard]] static inline ProcessReturnType<Processor> CallProcessor(
const Span<element_type>& aData, Processor&& aProcessor) {
MOZ_ASSERT(
aData.IsEmpty() || aData.Elements(),
"We expect a non-null data pointer for typed arrays that aren't empty");
return aProcessor(aData);
}
struct MOZ_STACK_CLASS LengthPinner {
explicit LengthPinner(const TypedArray_base* aTypedArray)
: mTypedArray(aTypedArray),
mWasPinned(
!JS::PinArrayBufferOrViewLength(aTypedArray->Obj(), true)) {}
~LengthPinner() {
if (!mWasPinned) {
JS::PinArrayBufferOrViewLength(mTypedArray->Obj(), false);
}
}
private:
const TypedArray_base* mTypedArray;
bool mWasPinned;
};
template <typename Processor, typename Calculator>
[[nodiscard]] bool ProcessDataHelper(
Processor&& aProcessor, Calculator&& aCalculateOffsetAndLength) const {
LengthPinner pinner(this);
JS::AutoCheckCannotGC nogc; // `data` is GC-sensitive.
Span<element_type> data = GetCurrentData();
const auto& offsetAndLength = aCalculateOffsetAndLength(data.Length());
size_t offset, length;
if constexpr (std::is_convertible_v<decltype(offsetAndLength),
std::pair<size_t, size_t>>) {
std::tie(offset, length) = offsetAndLength;
} else {
if (offsetAndLength.isNothing()) {
return false;
}
std::tie(offset, length) = offsetAndLength.value();
}
return CallProcessorNoGC(data.Subspan(offset, length),
std::forward<Processor>(aProcessor),
std::move(nogc));
}
template <typename Processor>
[[nodiscard]] ProcessNoGCReturnType<Processor> ProcessDataHelper(
Processor&& aProcessor) const {
LengthPinner pinner(this);
// The data from GetCurrentData() is GC sensitive.
JS::AutoCheckCannotGC nogc;
return CallProcessorNoGC(
GetCurrentData(), std::forward<Processor>(aProcessor), std::move(nogc));
}
public:
template <typename Processor>
[[nodiscard]] ProcessNoGCReturnType<Processor> ProcessData(
Processor&& aProcessor) const {
return ProcessDataHelper(std::forward<Processor>(aProcessor));
}
template <typename Processor>
[[nodiscard]] ProcessReturnType<Processor> ProcessFixedData(
Processor&& aProcessor) const {
mozilla::dom::AutoJSAPI jsapi;
if (!jsapi.Init(mImplObj) ||
!JS::EnsureNonInlineArrayBufferOrView(jsapi.cx(), mImplObj)) {
MOZ_CRASH("small oom when moving inline data out-of-line");
}
LengthPinner pinner(this);
return CallProcessor(GetCurrentData(), std::forward<Processor>(aProcessor));
}
private:
Span<element_type> GetCurrentData() const {
inline void ComputeState() const {
MOZ_ASSERT(inited());
if (!mComputed) {
size_t length;
JS::AutoCheckCannotGC nogc;
mData = ArrayT::fromObject(mImplObj).getLengthAndData(&length, &mShared,
nogc);
MOZ_RELEASE_ASSERT(length <= INT32_MAX,
"Bindings must have checked ArrayBuffer{View} length");
mLength = length;
mComputed = true;
}
return Span(mData, mLength);
MOZ_ASSERT(!mComputed);
size_t length;
JS::AutoCheckCannotGC nogc;
mData =
ArrayT::fromObject(mImplObj).getLengthAndData(&length, &mShared, nogc);
MOZ_RELEASE_ASSERT(length <= INT32_MAX,
"Bindings must have checked ArrayBuffer{View} length");
mLength = length;
mComputed = true;
}
template <typename T, typename F, typename... Calculator>
[[nodiscard]] Maybe<T> CreateFromDataHelper(
F&& aCreator, Calculator&&... aCalculator) const {
Maybe<T> result;
bool ok = ProcessDataHelper(
[&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) {
result.emplace();
return aCreator(aData, *result);
},
std::forward<Calculator>(aCalculator)...);
if (!ok) {
return Nothing();
}
return result;
inline void Reset() {
// This method mostly exists to inform the GC rooting hazard analysis that
// the variable can be considered dead, at least until you do anything else
// with it.
mData = nullptr;
mLength = 0;
mShared = false;
mComputed = false;
}
private:
TypedArray_base(const TypedArray_base&) = delete;
};
@ -831,152 +285,6 @@ class TypedArrayCreator {
const ArrayType& mArray;
};
namespace binding_detail {
template <typename Union, typename UnionMemberType, typename = int>
struct ApplyToTypedArray;
#define APPLY_IMPL(type) \
template <typename Union> \
struct ApplyToTypedArray<Union, type, decltype((void)&Union::Is##type, 0)> { \
/* Return type of calling the lambda with a TypedArray 'type'. */ \
template <typename F> \
using FunReturnType = decltype(std::declval<F>()(std::declval<type>())); \
\
/* Whether the return type of calling the lambda with a TypedArray */ \
/* 'type' is void. */ \
template <typename F> \
static constexpr bool FunReturnsVoid = \
std::is_same_v<FunReturnType<F>, void>; \
\
/* The return type of calling Apply with a union that has 'type' as */ \
/* one of its union member types depends on the return type of */ \
/* calling the lambda. This return type will be bool if the lambda */ \
/* returns void, or it will be a Maybe<…> with the inner type being */ \
/* the actual return type of calling the lambda. If the union */ \
/* contains a value of the right type, then calling Apply will return */ \
/* either 'true', or 'Some(…)' containing the return value of calling */ \
/* the lambda. If the union does not contain a value of the right */ \
/* type, then calling Apply will return either 'false', or */ \
/* 'Nothing()'. */ \
template <typename F> \
using ApplyReturnType = \
std::conditional_t<FunReturnsVoid<F>, bool, Maybe<FunReturnType<F>>>; \
\
public: \
template <typename F> \
static ApplyReturnType<F> Apply(const Union& aUnion, F&& aFun) { \
if (!aUnion.Is##type()) { \
return ApplyReturnType<F>(); /* false or Nothing() */ \
} \
if constexpr (FunReturnsVoid<F>) { \
std::forward<F>(aFun)(aUnion.GetAs##type()); \
return true; \
} else { \
return Some(std::forward<F>(aFun)(aUnion.GetAs##type())); \
} \
} \
};
APPLY_IMPL(Int8Array)
APPLY_IMPL(Uint8Array)
APPLY_IMPL(Uint8ClampedArray)
APPLY_IMPL(Int16Array)
APPLY_IMPL(Uint16Array)
APPLY_IMPL(Int32Array)
APPLY_IMPL(Uint32Array)
APPLY_IMPL(Float32Array)
APPLY_IMPL(Float64Array)
APPLY_IMPL(ArrayBufferView)
APPLY_IMPL(ArrayBuffer)
#undef APPLY_IMPL
// The binding code generate creates an alias of this type for every WebIDL
// union that contains a typed array type, with the right value for H (which
// will be true if there are non-typedarray types in the union).
template <typename T, bool H, typename FirstUnionMember,
typename... UnionMembers>
struct ApplyToTypedArraysHelper {
static constexpr bool HasNonTypedArrayMembers = H;
template <typename Fun>
static auto Apply(const T& aUnion, Fun&& aFun) {
auto result = ApplyToTypedArray<T, FirstUnionMember>::Apply(
aUnion, std::forward<Fun>(aFun));
if constexpr (sizeof...(UnionMembers) == 0) {
return result;
} else {
if (result) {
return result;
} else {
return ApplyToTypedArraysHelper<T, H, UnionMembers...>::template Apply<
Fun>(aUnion, std::forward<Fun>(aFun));
}
}
}
};
template <typename T, typename Fun>
auto ApplyToTypedArrays(const T& aUnion, Fun&& aFun) {
using ApplyToTypedArrays = typename T::ApplyToTypedArrays;
auto result =
ApplyToTypedArrays::template Apply<Fun>(aUnion, std::forward<Fun>(aFun));
if constexpr (ApplyToTypedArrays::HasNonTypedArrayMembers) {
return result;
} else {
MOZ_ASSERT(result, "Didn't expect union members other than typed arrays");
if constexpr (std::is_same_v<std::remove_cv_t<
std::remove_reference_t<decltype(result)>>,
bool>) {
return;
} else {
return result.extract();
}
}
}
} // namespace binding_detail
template <typename T, typename ToType,
std::enable_if_t<is_dom_union_with_typedarray_members<T>, int> = 0>
[[nodiscard]] auto AppendTypedArrayDataTo(const T& aUnion, ToType& aResult) {
return binding_detail::ApplyToTypedArrays(
aUnion, [&](const auto& aTypedArray) {
return aTypedArray.AppendDataTo(aResult);
});
}
template <typename ToType, typename T,
std::enable_if_t<is_dom_union_with_typedarray_members<T>, int> = 0>
[[nodiscard]] auto CreateFromTypedArrayData(const T& aUnion) {
return binding_detail::ApplyToTypedArrays(
aUnion, [&](const auto& aTypedArray) {
return aTypedArray.template CreateFromData<ToType>();
});
}
template <typename T, typename Processor,
std::enable_if_t<is_dom_union_with_typedarray_members<T>, int> = 0>
[[nodiscard]] auto ProcessTypedArrays(const T& aUnion, Processor&& aProcessor) {
return binding_detail::ApplyToTypedArrays(
aUnion, [&](const auto& aTypedArray) {
return aTypedArray.ProcessData(std::forward<Processor>(aProcessor));
});
}
template <typename T, typename Processor,
std::enable_if_t<is_dom_union_with_typedarray_members<T>, int> = 0>
[[nodiscard]] auto ProcessTypedArraysFixed(const T& aUnion,
Processor&& aProcessor) {
return binding_detail::ApplyToTypedArrays(
aUnion, [&](const auto& aTypedArray) {
return aTypedArray.ProcessFixedData(
std::forward<Processor>(aProcessor));
});
}
} // namespace mozilla::dom
#endif /* mozilla_dom_TypedArray_h */

View File

@ -731,39 +731,7 @@ class TestInterface : public nsISupports, public nsWrapperCache {
// void PassUnionWithInterfaces(const TestInterfaceOrTestExternalInterface&
// arg); void PassUnionWithInterfacesAndNullable(const
// TestInterfaceOrNullOrTestExternalInterface& arg);
void PassUnionWithArrayBuffer(const UTF8StringOrArrayBuffer& aArg) {
auto processor = [](const Span<uint8_t>& aData) -> int { return -1; };
static_assert(
std::is_same_v<decltype(ProcessTypedArraysFixed(aArg, processor)),
Maybe<int>>,
"If the union can contain non-typedarray members we need to signal "
"that with a Maybe<…> rv.");
}
void PassUnionWithArrayBufferOrNull(
const UTF8StringOrArrayBufferOrNull& aArg) {
auto processor = [](const Span<uint8_t>& aData) -> int { return -1; };
static_assert(
std::is_same_v<decltype(ProcessTypedArraysFixed(aArg, processor)),
Maybe<int>>,
"If the union can contain non-typedarray members or null we need to "
"signal that with a Maybe<…> rv.");
}
void PassUnionWithTypedArrays(const ArrayBufferViewOrArrayBuffer& aArg) {
auto processor = [](const Span<uint8_t>& aData) -> int { return -1; };
static_assert(
std::is_same_v<decltype(ProcessTypedArraysFixed(aArg, processor)), int>,
"If the union can't contain non-typedarray members or null we can just "
"return the result of calling the lambda.");
}
void PassUnionWithTypedArraysOrNull(
const ArrayBufferViewOrArrayBufferOrNull& aArg) {
auto processor = [](const Span<uint8_t>& aData) -> int { return -1; };
static_assert(
std::is_same_v<decltype(ProcessTypedArraysFixed(aArg, processor)),
Maybe<int>>,
"If the union can contain non-typedarray members or null we need to "
"signal that with a Maybe<…> rv.");
}
void PassUnionWithArrayBuffer(const ArrayBufferOrLong&);
void PassUnionWithString(JSContext*, const StringOrObject&);
void PassUnionWithEnum(JSContext*, const SupportedTypeOrObject&);
// void PassUnionWithCallback(JSContext*, const TestCallbackOrLong&);

View File

@ -700,10 +700,7 @@ interface TestInterface {
//undefined passUnionWithInterfaces((TestInterface or TestExternalInterface) arg);
//undefined passUnionWithInterfacesAndNullable((TestInterface? or TestExternalInterface) arg);
//undefined passUnionWithSequence((sequence<object> or long) arg);
undefined passUnionWithArrayBuffer((UTF8String or ArrayBuffer) arg);
undefined passUnionWithArrayBufferOrNull((UTF8String or ArrayBuffer?) arg);
undefined passUnionWithTypedArrays((ArrayBufferView or ArrayBuffer) arg);
undefined passUnionWithTypedArraysOrNull((ArrayBufferView or ArrayBuffer?) arg);
undefined passUnionWithArrayBuffer((ArrayBuffer or long) arg);
undefined passUnionWithString((DOMString or object) arg);
// Using an enum in a union. Note that we use some enum not declared in our
// binding file, because UnionTypes.h will need to include the binding header

View File

@ -495,10 +495,7 @@ interface TestExampleInterface {
//undefined passUnionWithInterfaces((TestInterface or TestExternalInterface) arg);
//undefined passUnionWithInterfacesAndNullable((TestInterface? or TestExternalInterface) arg);
//undefined passUnionWithSequence((sequence<object> or long) arg);
undefined passUnionWithArrayBuffer((UTF8String or ArrayBuffer) arg);
undefined passUnionWithArrayBufferOrNull((UTF8String or ArrayBuffer?) arg);
undefined passUnionWithTypedArrays((ArrayBufferView or ArrayBuffer) arg);
undefined passUnionWithTypedArraysOrNull((ArrayBufferView or ArrayBuffer?) arg);
undefined passUnionWithArrayBuffer((ArrayBuffer or long) arg);
undefined passUnionWithString((DOMString or object) arg);
// Using an enum in a union. Note that we use some enum not declared in our
// binding file, because UnionTypes.h will need to include the binding header

View File

@ -508,10 +508,7 @@ interface TestJSImplInterface {
//undefined passUnionWithInterfaces((TestJSImplInterface or TestExternalInterface) arg);
//undefined passUnionWithInterfacesAndNullable((TestJSImplInterface? or TestExternalInterface) arg);
//undefined passUnionWithSequence((sequence<object> or long) arg);
undefined passUnionWithArrayBuffer((UTF8String or ArrayBuffer) arg);
undefined passUnionWithArrayBufferOrNull((UTF8String or ArrayBuffer?) arg);
undefined passUnionWithTypedArrays((ArrayBufferView or ArrayBuffer) arg);
undefined passUnionWithTypedArraysOrNull((ArrayBufferView or ArrayBuffer?) arg);
undefined passUnionWithArrayBuffer((ArrayBuffer or long) arg);
undefined passUnionWithString((DOMString or object) arg);
// Using an enum in a union. Note that we use some enum not declared in our
// binding file, because UnionTypes.h will need to include the binding header

View File

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

View File

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

View File

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

View File

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

View File

@ -51,7 +51,8 @@ already_AddRefed<ImageData> ImageData::Constructor(const GlobalObject& aGlobal,
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;
if (!length.isValid() || length.value() > INT32_MAX) {
aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
@ -72,11 +73,9 @@ already_AddRefed<ImageData> ImageData::Constructor(
const GlobalObject& aGlobal, const Uint8ClampedArray& aData,
const uint32_t aWidth, const Optional<uint32_t>& aHeight,
ErrorResult& aRv) {
Maybe<uint32_t> maybeLength = aData.ProcessData(
[&](const Span<uint8_t>& aData, JS::AutoCheckCannotGC&& nogc) {
return Some(aData.Length());
});
uint32_t length = maybeLength.valueOr(0);
aData.ComputeState();
uint32_t length = aData.Length();
if (length == 0 || length % 4) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;

View File

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

View File

@ -38,30 +38,46 @@ uint8_t* CryptoBuffer::Assign(const nsTArray<uint8_t>& aData) {
}
uint8_t* CryptoBuffer::Assign(const ArrayBuffer& aData) {
Clear();
return aData.AppendDataTo(*this) ? Elements() : nullptr;
aData.ComputeState();
return Assign(aData.Data(), aData.Length());
}
uint8_t* CryptoBuffer::Assign(const ArrayBufferView& aData) {
Clear();
return aData.AppendDataTo(*this) ? Elements() : nullptr;
aData.ComputeState();
return Assign(aData.Data(), aData.Length());
}
uint8_t* CryptoBuffer::Assign(const ArrayBufferViewOrArrayBuffer& aData) {
Clear();
if (aData.IsArrayBufferView()) {
return Assign(aData.GetAsArrayBufferView());
}
if (aData.IsArrayBuffer()) {
return Assign(aData.GetAsArrayBuffer());
}
return AppendTypedArrayDataTo(aData, *this) ? Elements() : nullptr;
// If your union is uninitialized, something's wrong
MOZ_ASSERT(false);
Clear();
return nullptr;
}
uint8_t* CryptoBuffer::Assign(const OwningArrayBufferViewOrArrayBuffer& aData) {
Clear();
if (aData.IsArrayBufferView()) {
return Assign(aData.GetAsArrayBufferView());
}
if (aData.IsArrayBuffer()) {
return Assign(aData.GetAsArrayBuffer());
}
return AppendTypedArrayDataTo(aData, *this) ? Elements() : nullptr;
// If your union is uninitialized, something's wrong
MOZ_ASSERT(false);
Clear();
return nullptr;
}
uint8_t* CryptoBuffer::Assign(const Uint8Array& aArray) {
Clear();
return aArray.AppendDataTo(*this) ? Elements() : nullptr;
aArray.ComputeState();
return Assign(aArray.Data(), aArray.Length());
}
uint8_t* CryptoBuffer::AppendSECItem(const SECItem* aItem) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -232,14 +232,6 @@ class MOZ_STACK_CLASS ContentEventHandler {
#endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
public:
const RawNodePosition& operator=(const RawNodePosition& aOther) {
if (this != &aOther) {
RawRangeBoundary::operator=(aOther);
mAfterOpenTag = aOther.mAfterOpenTag;
}
return *this;
}
bool operator==(const RawNodePosition& aOther) const {
return RawRangeBoundary::operator==(aOther) &&
mAfterOpenTag == aOther.mAfterOpenTag;

View File

@ -21,21 +21,18 @@
namespace mozilla::dom {
static nsresult GetBufferDataAsStream(Vector<uint8_t>&& aData,
nsIInputStream** aResult,
uint64_t* aContentLength,
nsACString& aContentType,
nsACString& aCharset) {
static nsresult GetBufferDataAsStream(
const uint8_t* aData, uint32_t aDataLength, nsIInputStream** aResult,
uint64_t* aContentLength, nsACString& aContentType, nsACString& aCharset) {
aContentType.SetIsVoid(true);
aCharset.Truncate();
*aContentLength = aData.length();
*aContentLength = aDataLength;
const char* data = reinterpret_cast<const char*>(aData);
nsCOMPtr<nsIInputStream> stream;
nsresult rv = NS_NewByteInputStream(
getter_AddRefs(stream),
AsChars(Span(aData.extractOrCopyRawBuffer(), *aContentLength)),
NS_ASSIGNMENT_ADOPT);
getter_AddRefs(stream), Span(data, aDataLength), NS_ASSIGNMENT_COPY);
NS_ENSURE_SUCCESS(rv, rv);
stream.forget(aResult);
@ -47,24 +44,20 @@ template <>
nsresult BodyExtractor<const ArrayBuffer>::GetAsStream(
nsIInputStream** aResult, uint64_t* aContentLength,
nsACString& aContentTypeWithCharset, nsACString& aCharset) const {
Maybe<Vector<uint8_t>> body = mBody->CreateFromData<Vector<uint8_t>>();
if (body.isNothing()) {
return NS_ERROR_OUT_OF_MEMORY;
}
return GetBufferDataAsStream(body.extract(), aResult, aContentLength,
aContentTypeWithCharset, aCharset);
mBody->ComputeState();
return GetBufferDataAsStream(mBody->Data(), mBody->Length(), aResult,
aContentLength, aContentTypeWithCharset,
aCharset);
}
template <>
nsresult BodyExtractor<const ArrayBufferView>::GetAsStream(
nsIInputStream** aResult, uint64_t* aContentLength,
nsACString& aContentTypeWithCharset, nsACString& aCharset) const {
Maybe<Vector<uint8_t>> body = mBody->CreateFromData<Vector<uint8_t>>();
if (body.isNothing()) {
return NS_ERROR_OUT_OF_MEMORY;
}
return GetBufferDataAsStream(body.extract(), aResult, aContentLength,
aContentTypeWithCharset, aCharset);
mBody->ComputeState();
return GetBufferDataAsStream(mBody->Data(), mBody->Length(), aResult,
aContentLength, aContentTypeWithCharset,
aCharset);
}
template <>

View File

@ -325,29 +325,21 @@ void FetchStreamReader::ChunkSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
CloseAndRelease(aCx, NS_ERROR_DOM_WRONG_TYPE_ERR);
return;
}
chunk.ComputeState();
MOZ_DIAGNOSTIC_ASSERT(mBuffer.IsEmpty());
// Let's take a copy of the data.
// FIXME: We could sometimes avoid this copy by trying to write `chunk`
// directly into `mPipeOut` eagerly, and only filling `mBuffer` if there isn't
// enough space in the pipe's buffer.
nsTArray<uint8_t> buffer;
if (!chunk.AppendDataTo(buffer)) {
if (!mBuffer.AppendElements(chunk.Data(), chunk.Length(), fallible)) {
CloseAndRelease(aCx, NS_ERROR_OUT_OF_MEMORY);
return;
}
uint32_t len = buffer.Length();
if (len == 0) {
// If there is nothing to read, let's do another reading.
OnOutputStreamReady(mPipeOut);
return;
}
MOZ_DIAGNOSTIC_ASSERT(mBuffer.IsEmpty());
mBuffer = std::move(buffer);
mBufferOffset = 0;
mBufferRemaining = mBuffer.Length();
mBufferRemaining = chunk.Length();
nsresult rv = WriteBuffer();
if (NS_WARN_IF(NS_FAILED(rv))) {

View File

@ -331,7 +331,8 @@ nsresult InternalResponse::GeneratePaddingInfo() {
MOZ_DIAGNOSTIC_ASSERT(randomGenerator);
rv = randomGenerator->GenerateRandomBytesInto(randomNumber);
uint8_t* buffer;
rv = randomGenerator->GenerateRandomBytes(sizeof(randomNumber), &buffer);
if (NS_WARN_IF(NS_FAILED(rv))) {
Maybe<uint64_t> maybeRandomNum = RandomUint64();
if (maybeRandomNum.isSome()) {
@ -341,6 +342,9 @@ nsresult InternalResponse::GeneratePaddingInfo() {
return rv;
}
memcpy(&randomNumber, buffer, sizeof(randomNumber));
free(buffer);
mPaddingInfo.emplace(randomNumber % kMaxRandomNumber);
return rv;

View File

@ -30,13 +30,6 @@ nsresult BlobSet::AppendVoidPtr(const void* aData, uint32_t aLength) {
return AppendBlobImpl(blobImpl);
}
nsresult BlobSet::AppendVector(Vector<uint8_t>&& aData) {
size_t length = aData.length();
RefPtr<BlobImpl> blobImpl =
new MemoryBlobImpl(aData.extractOrCopyRawBuffer(), length, u""_ns);
return AppendBlobImpl(blobImpl);
}
nsresult BlobSet::AppendUTF8String(const nsACString& aUTF8String,
bool nativeEOL) {
nsCString utf8Str;

View File

@ -20,8 +20,6 @@ class BlobSet final {
public:
[[nodiscard]] nsresult AppendVoidPtr(const void* aData, uint32_t aLength);
[[nodiscard]] nsresult AppendVector(Vector<uint8_t>&& aData);
[[nodiscard]] nsresult AppendUTF8String(const nsACString& aUTF8String,
bool nativeEOL);

View File

@ -210,23 +210,27 @@ void MultipartBlobImpl::InitializeBlob(const Sequence<Blob::BlobPart>& aData,
}
}
else {
auto blobData = CreateFromTypedArrayData<Vector<uint8_t>>(data);
if (blobData.isNothing()) {
MOZ_CRASH("Impossible blob data type.");
}
if (!blobData.ref()) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
aRv = blobSet.AppendVector(blobData.ref().extract());
else if (data.IsArrayBuffer()) {
const ArrayBuffer& buffer = data.GetAsArrayBuffer();
buffer.ComputeState();
aRv = blobSet.AppendVoidPtr(buffer.Data(), buffer.Length());
if (aRv.Failed()) {
return;
}
}
else if (data.IsArrayBufferView()) {
const ArrayBufferView& buffer = data.GetAsArrayBufferView();
buffer.ComputeState();
aRv = blobSet.AppendVoidPtr(buffer.Data(), buffer.Length());
if (aRv.Failed()) {
return;
}
}
else {
MOZ_CRASH("Impossible blob data type.");
}
}
mBlobImpls = blobSet.GetBlobImpls();

View File

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

View File

@ -841,18 +841,23 @@ RefPtr<Int64Promise> FileSystemWritableFileStream::Write(
// https://fs.spec.whatwg.org/#write-a-chunk
// Step 3.4.6 If data is a BufferSource, let dataBytes be a copy of data.
auto vectorFromTypedArray = CreateFromTypedArrayData<Vector<uint8_t>>(aData);
if (vectorFromTypedArray.isSome()) {
Maybe<Vector<uint8_t>>& maybeVector = vectorFromTypedArray.ref();
QM_TRY(MOZ_TO_RESULT(maybeVector.isSome()), CreateAndRejectInt64Promise);
if (aData.IsArrayBuffer() || aData.IsArrayBufferView()) {
const auto dataSpan = [&aData]() -> mozilla::Span<uint8_t> {
if (aData.IsArrayBuffer()) {
const ArrayBuffer& buffer = aData.GetAsArrayBuffer();
buffer.ComputeState();
return Span{buffer.Data(), buffer.Length()};
}
const ArrayBufferView& buffer = aData.GetAsArrayBufferView();
buffer.ComputeState();
return Span{buffer.Data(), buffer.Length()};
}();
// Here we copy
size_t length = maybeVector->length();
QM_TRY(MOZ_TO_RESULT(NS_NewByteInputStream(
getter_AddRefs(inputStream),
AsChars(Span(maybeVector->extractOrCopyRawBuffer(), length)),
NS_ASSIGNMENT_ADOPT)),
QM_TRY(MOZ_TO_RESULT(NS_NewByteInputStream(getter_AddRefs(inputStream),
AsChars(dataSpan),
NS_ASSIGNMENT_COPY)),
CreateAndRejectInt64Promise);
return WriteImpl(mTaskQueue, std::move(inputStream), mStreamOwner,

View File

@ -269,43 +269,55 @@ already_AddRefed<Promise> GamepadServiceTest::NewPoseMove(
GamepadCapabilityFlags::Cap_AngularAcceleration |
GamepadCapabilityFlags::Cap_LinearAcceleration;
if (!aOrient.IsNull()) {
DebugOnly<bool> ok = aOrient.Value().CopyDataTo(poseState.orientation);
MOZ_ASSERT(
ok, "aOrient.Value().Length() != ArrayLength(poseState.orientation)");
const Float32Array& value = aOrient.Value();
value.ComputeState();
MOZ_ASSERT(value.Length() == 4);
poseState.orientation[0] = value.Data()[0];
poseState.orientation[1] = value.Data()[1];
poseState.orientation[2] = value.Data()[2];
poseState.orientation[3] = value.Data()[3];
poseState.isOrientationValid = true;
}
if (!aPos.IsNull()) {
DebugOnly<bool> ok = aPos.Value().CopyDataTo(poseState.position);
MOZ_ASSERT(ok, "aPos.Value().Length() != ArrayLength(poseState.position)");
const Float32Array& value = aPos.Value();
value.ComputeState();
MOZ_ASSERT(value.Length() == 3);
poseState.position[0] = value.Data()[0];
poseState.position[1] = value.Data()[1];
poseState.position[2] = value.Data()[2];
poseState.isPositionValid = true;
}
if (!aAngVelocity.IsNull()) {
DebugOnly<bool> ok =
aAngVelocity.Value().CopyDataTo(poseState.angularVelocity);
MOZ_ASSERT(ok,
"aAngVelocity.Value().Length() != "
"ArrayLength(poseState.angularVelocity)");
const Float32Array& value = aAngVelocity.Value();
value.ComputeState();
MOZ_ASSERT(value.Length() == 3);
poseState.angularVelocity[0] = value.Data()[0];
poseState.angularVelocity[1] = value.Data()[1];
poseState.angularVelocity[2] = value.Data()[2];
}
if (!aAngAcceleration.IsNull()) {
DebugOnly<bool> ok =
aAngAcceleration.Value().CopyDataTo(poseState.angularAcceleration);
MOZ_ASSERT(ok,
"aAngAcceleration.Value().Length() != "
"ArrayLength(poseState.angularAcceleration)");
const Float32Array& value = aAngAcceleration.Value();
value.ComputeState();
MOZ_ASSERT(value.Length() == 3);
poseState.angularAcceleration[0] = value.Data()[0];
poseState.angularAcceleration[1] = value.Data()[1];
poseState.angularAcceleration[2] = value.Data()[2];
}
if (!aLinVelocity.IsNull()) {
DebugOnly<bool> ok =
aLinVelocity.Value().CopyDataTo(poseState.linearVelocity);
MOZ_ASSERT(ok,
"aLinVelocity.Value().Length() != "
"ArrayLength(poseState.linearVelocity)");
const Float32Array& value = aLinVelocity.Value();
value.ComputeState();
MOZ_ASSERT(value.Length() == 3);
poseState.linearVelocity[0] = value.Data()[0];
poseState.linearVelocity[1] = value.Data()[1];
poseState.linearVelocity[2] = value.Data()[2];
}
if (!aLinAcceleration.IsNull()) {
DebugOnly<bool> ok =
aLinAcceleration.Value().CopyDataTo(poseState.linearAcceleration);
MOZ_ASSERT(ok,
"aLinAcceleration.Value().Length() != "
"ArrayLength(poseState.linearAcceleration)");
const Float32Array& value = aLinAcceleration.Value();
value.ComputeState();
MOZ_ASSERT(value.Length() == 3);
poseState.linearAcceleration[0] = value.Data()[0];
poseState.linearAcceleration[1] = value.Data()[1];
poseState.linearAcceleration[2] = value.Data()[2];
}
GamepadPoseInformation a(poseState);
@ -338,13 +350,18 @@ already_AddRefed<Promise> GamepadServiceTest::NewTouch(
GamepadTouchState touchState;
touchState.touchId = aTouchId;
touchState.surfaceId = aSurfaceId;
DebugOnly<bool> ok = aPos.CopyDataTo(touchState.position);
MOZ_ASSERT(ok, "aPos.Length() != ArrayLength(touchState.position)");
const Float32Array& value = aPos;
value.ComputeState();
MOZ_ASSERT(value.Length() == 2);
touchState.position[0] = value.Data()[0];
touchState.position[1] = value.Data()[1];
if (!aSurfDim.IsNull()) {
ok = aSurfDim.Value().CopyDataTo(touchState.surfaceDimensions);
MOZ_ASSERT(
ok, "aSurfDim.Length() != ArrayLength(touchState.surfaceDimensions)");
const Float32Array& value = aSurfDim.Value();
value.ComputeState();
MOZ_ASSERT(value.Length() == 2);
touchState.surfaceDimensions[0] = value.Data()[0];
touchState.surfaceDimensions[1] = value.Data()[1];
touchState.isSurfaceDimensionsValid = true;
}

View File

@ -23,11 +23,34 @@ LogModule* GetEMEVerboseLog() {
return log;
}
ArrayData GetArrayBufferViewOrArrayBufferData(
const dom::ArrayBufferViewOrArrayBuffer& aBufferOrView) {
MOZ_ASSERT(aBufferOrView.IsArrayBuffer() ||
aBufferOrView.IsArrayBufferView());
JS::AutoCheckCannotGC nogc;
if (aBufferOrView.IsArrayBuffer()) {
const dom::ArrayBuffer& buffer = aBufferOrView.GetAsArrayBuffer();
buffer.ComputeState();
return ArrayData(buffer.Data(), buffer.Length());
} else if (aBufferOrView.IsArrayBufferView()) {
const dom::ArrayBufferView& bufferview =
aBufferOrView.GetAsArrayBufferView();
bufferview.ComputeState();
return ArrayData(bufferview.Data(), bufferview.Length());
}
return ArrayData(nullptr, 0);
}
void CopyArrayBufferViewOrArrayBufferData(
const dom::ArrayBufferViewOrArrayBuffer& aBufferOrView,
nsTArray<uint8_t>& aOutData) {
JS::AutoCheckCannotGC nogc;
ArrayData data = GetArrayBufferViewOrArrayBufferData(aBufferOrView);
aOutData.Clear();
Unused << dom::AppendTypedArrayDataTo(aBufferOrView, aOutData);
if (!data.IsValid()) {
return;
}
aOutData.AppendElements(data.mData, data.mLength);
}
bool IsClearkeyKeySystem(const nsAString& aKeySystem) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -330,12 +330,17 @@ void WaveShaperNode::SetCurve(const Nullable<Float32Array>& aCurve,
return;
}
const Float32Array& floats = aCurve.Value();
floats.ComputeState();
nsTArray<float> curve;
if (!aCurve.Value().AppendDataTo(curve)) {
uint32_t argLength = floats.Length();
if (!curve.SetLength(argLength, fallible)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
PodCopy(curve.Elements(), floats.Data(), argLength);
SetCurveInternal(curve, aRv);
}

View File

@ -5,7 +5,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/EncodedVideoChunk.h"
#include <utility>
#include "mozilla/dom/EncodedVideoChunkBinding.h"
#include "MediaData.h"
@ -144,30 +143,32 @@ already_AddRefed<EncodedVideoChunk> EncodedVideoChunk::Constructor(
return nullptr;
}
auto buffer = ProcessTypedArrays(
aInit.mData,
[&](const Span<uint8_t>& aData,
JS::AutoCheckCannotGC&&) -> RefPtr<MediaAlignedByteBuffer> {
// Make sure it's in uint32_t's range.
CheckedUint32 byteLength(aData.Length());
if (!byteLength.isValid()) {
aRv.Throw(NS_ERROR_INVALID_ARG);
return nullptr;
}
if (aData.Length() == 0) {
LOGW("Buffer for constructing EncodedVideoChunk is empty!");
}
RefPtr<MediaAlignedByteBuffer> buf = MakeRefPtr<MediaAlignedByteBuffer>(
aData.Elements(), aData.Length());
auto r = GetSharedArrayBufferData(aInit.mData);
if (r.isErr()) {
aRv.Throw(r.unwrapErr());
return nullptr;
}
Span<uint8_t> buf = r.unwrap();
// Instead of checking *buf, size comparision is used to allow
// constructing a zero-sized EncodedVideoChunk.
if (!buf || buf->Size() != aData.Length()) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return nullptr;
}
return buf;
});
// Make sure it's in uint32_t's range.
CheckedUint32 byteLength(buf.size_bytes());
if (!byteLength.isValid()) {
aRv.Throw(NS_ERROR_INVALID_ARG);
return nullptr;
}
if (buf.size_bytes() == 0) {
LOGW("Buffer for constructing EncodedVideoChunk is empty!");
}
auto buffer =
MakeRefPtr<MediaAlignedByteBuffer>(buf.data(), buf.size_bytes());
// Instead of checking *buffer, size comparision is used to allow constructing
// a zero-sized EncodedVideoChunk.
if (!buffer || buffer->Size() != buf.size_bytes()) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return nullptr;
}
RefPtr<EncodedVideoChunk> chunk(new EncodedVideoChunk(
global, buffer.forget(), aInit.mType, aInit.mTimestamp,
@ -205,15 +206,20 @@ void EncodedVideoChunk::CopyTo(
ErrorResult& aRv) {
AssertIsOnOwningThread();
ProcessTypedArraysFixed(aDestination, [&](const Span<uint8_t>& aData) {
if (mBuffer->Size() > aData.size_bytes()) {
aRv.ThrowTypeError(
"Destination ArrayBuffer smaller than source EncodedVideoChunk");
return;
}
auto r = GetSharedArrayBufferData(aDestination);
if (r.isErr()) {
aRv.Throw(r.unwrapErr());
return;
}
Span<uint8_t> buf = r.unwrap();
PodCopy(aData.data(), mBuffer->Data(), mBuffer->Size());
});
if (mBuffer->Size() > buf.size_bytes()) {
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

View File

@ -9,9 +9,9 @@
#include "js/TypeDecls.h"
#include "mozilla/Attributes.h"
#include "mozilla/Buffer.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/Maybe.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "nsCycleCollectionParticipant.h"
#include "nsWrapperCache.h"

View File

@ -233,9 +233,15 @@ static nsTArray<UniquePtr<TrackInfo>> GetTracksInfo(MIMECreateParam aParam) {
static Result<RefPtr<MediaByteBuffer>, nsresult> GetExtraData(
const OwningMaybeSharedArrayBufferViewOrMaybeSharedArrayBuffer& aBuffer) {
RefPtr<MediaByteBuffer> data = MakeRefPtr<MediaByteBuffer>();
Unused << AppendTypedArrayDataTo(aBuffer, *data);
return data->Length() > 0 ? data : nullptr;
RefPtr<MediaByteBuffer> data = nullptr;
Span<uint8_t> buf;
MOZ_TRY_VAR(buf, GetSharedArrayBufferData(aBuffer));
if (buf.empty()) {
return data;
}
data = MakeRefPtr<MediaByteBuffer>();
data->AppendElements(buf);
return data;
}
static Result<UniquePtr<TrackInfo>, nsresult> CreateVideoInfo(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -811,39 +811,27 @@ bool TCPSocket::Send(const ArrayBuffer& aData, uint32_t aByteOffset,
nsCOMPtr<nsIArrayBufferInputStream> stream;
uint32_t nbytes;
auto calculateOffsetAndCount = [&](uint32_t aLength) {
uint32_t offset = std::min(aLength, aByteOffset);
nbytes = std::min(aLength - aByteOffset,
aByteLength.WasPassed() ? aByteLength.Value() : aLength);
return std::pair(offset, nbytes);
};
aData.ComputeState();
uint32_t byteLength =
aByteLength.WasPassed() ? aByteLength.Value() : aData.Length();
if (mSocketBridgeChild) {
nsTArray<uint8_t> arrayBuffer;
if (!aData.AppendDataTo(arrayBuffer, calculateOffsetAndCount)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
nsresult rv = mSocketBridgeChild->SendSend(aData, aByteOffset, byteLength);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return false;
}
mSocketBridgeChild->SendSend(std::move(arrayBuffer));
} else {
mozilla::Maybe<mozilla::UniquePtr<uint8_t[]>> arrayBuffer =
aData.CreateFromData<mozilla::UniquePtr<uint8_t[]>>(
calculateOffsetAndCount);
if (arrayBuffer.isNothing()) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return false;
}
JS::Rooted<JS::Value> value(RootingCx(), JS::ObjectValue(*aData.Obj()));
stream = do_CreateInstance("@mozilla.org/io/arraybuffer-input-stream;1");
nsresult rv = stream->SetData(arrayBuffer.extract(), nbytes);
nsresult rv = stream->SetData(value, aByteOffset, byteLength);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return false;
}
}
return Send(stream, nbytes);
return Send(stream, byteLength);
}
bool TCPSocket::Send(nsIInputStream* aStream, uint32_t aByteLength) {

View File

@ -145,8 +145,19 @@ void TCPSocketChild::SendSend(const nsACString& aData) {
SendData(nsCString(aData));
}
void TCPSocketChild::SendSend(nsTArray<uint8_t>&& aData) {
SendData(SendableData{std::move(aData)});
nsresult TCPSocketChild::SendSend(const ArrayBuffer& aData,
uint32_t aByteOffset, uint32_t aByteLength) {
uint32_t buflen = aData.Length();
uint32_t offset = std::min(buflen, aByteOffset);
uint32_t nbytes = std::min(buflen - aByteOffset, aByteLength);
FallibleTArray<uint8_t> fallibleArr;
if (!fallibleArr.InsertElementsAt(0, aData.Data() + offset, nbytes,
fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
SendData(SendableData{std::move(fallibleArr)});
return NS_OK;
}
void TCPSocketChild::SetSocket(TCPSocket* aSocket) { mSocket = aSocket; }

View File

@ -52,7 +52,8 @@ class TCPSocketChild : public mozilla::net::PTCPSocketChild,
void SendOpen(nsITCPSocketCallback* aSocket, bool aUseSSL,
bool aUseArrayBuffers);
void SendSend(const nsACString& aData);
void SendSend(nsTArray<uint8_t>&& aData);
nsresult SendSend(const ArrayBuffer& aData, uint32_t aByteOffset,
uint32_t aByteLength);
void SetSocket(TCPSocket* aSocket);
void GetHost(nsAString& aHost);

View File

@ -341,14 +341,16 @@ bool UDPSocket::Send(const StringOrBlobOrArrayBufferOrArrayBufferView& aData,
if (aData.IsString()) {
NS_ConvertUTF16toUTF8 data(aData.GetAsString());
aRv = strStream->SetData(data.BeginReading(), data.Length());
} else if (aData.IsArrayBuffer()) {
const ArrayBuffer& data = aData.GetAsArrayBuffer();
data.ComputeState();
aRv = strStream->SetData(reinterpret_cast<const char*>(data.Data()),
data.Length());
} else {
Vector<char> data;
if (!AppendTypedArrayDataTo(aData, data)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return false;
}
size_t length = data.length();
aRv = strStream->AdoptData(data.extractOrCopyRawBuffer(), length);
const ArrayBufferView& data = aData.GetAsArrayBufferView();
data.ComputeState();
aRv = strStream->SetData(reinterpret_cast<const char*>(data.Data()),
data.Length());
}
if (NS_WARN_IF(aRv.Failed())) {

View File

@ -522,10 +522,18 @@ nsresult PushManager::NormalizeAppServerKey(
return NS_ERROR_DOM_INVALID_CHARACTER_ERR;
}
aAppServerKey = decodedKey;
} else {
if (!AppendTypedArrayDataTo(aSource, aAppServerKey)) {
} else if (aSource.IsArrayBuffer()) {
if (!PushUtil::CopyArrayBufferToArray(aSource.GetAsArrayBuffer(),
aAppServerKey)) {
return NS_ERROR_DOM_PUSH_INVALID_KEY_ERR;
}
} else if (aSource.IsArrayBufferView()) {
if (!PushUtil::CopyArrayBufferViewToArray(aSource.GetAsArrayBufferView(),
aAppServerKey)) {
return NS_ERROR_DOM_PUSH_INVALID_KEY_ERR;
}
} else {
MOZ_CRASH("Uninitialized union: expected string, buffer, or view");
}
if (aAppServerKey.IsEmpty()) {
return NS_ERROR_DOM_PUSH_INVALID_KEY_ERR;

View File

@ -228,7 +228,8 @@ already_AddRefed<PushSubscription> PushSubscription::Constructor(
nsTArray<uint8_t> rawKey;
if (aInitDict.mP256dhKey.WasPassed() &&
!aInitDict.mP256dhKey.Value().IsNull() &&
!aInitDict.mP256dhKey.Value().Value().AppendDataTo(rawKey)) {
!PushUtil::CopyArrayBufferToArray(aInitDict.mP256dhKey.Value().Value(),
rawKey)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return nullptr;
}
@ -236,7 +237,8 @@ already_AddRefed<PushSubscription> PushSubscription::Constructor(
nsTArray<uint8_t> authSecret;
if (aInitDict.mAuthSecret.WasPassed() &&
!aInitDict.mAuthSecret.Value().IsNull() &&
!aInitDict.mAuthSecret.Value().Value().AppendDataTo(authSecret)) {
!PushUtil::CopyArrayBufferToArray(aInitDict.mAuthSecret.Value().Value(),
authSecret)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return nullptr;
}

View File

@ -9,12 +9,35 @@
namespace mozilla::dom {
/* static */
bool PushUtil::CopyArrayBufferToArray(const ArrayBuffer& aBuffer,
nsTArray<uint8_t>& aArray) {
MOZ_ASSERT(aArray.IsEmpty());
aBuffer.ComputeState();
return aArray.SetCapacity(aBuffer.Length(), fallible) &&
aArray.InsertElementsAt(0, aBuffer.Data(), aBuffer.Length(), fallible);
}
/* static */
bool PushUtil::CopyArrayBufferViewToArray(const ArrayBufferView& aView,
nsTArray<uint8_t>& aArray) {
MOZ_ASSERT(aArray.IsEmpty());
aView.ComputeState();
return aArray.SetCapacity(aView.Length(), fallible) &&
aArray.InsertElementsAt(0, aView.Data(), aView.Length(), fallible);
}
/* static */
bool PushUtil::CopyBufferSourceToArray(
const OwningArrayBufferViewOrArrayBuffer& aSource,
nsTArray<uint8_t>& aArray) {
MOZ_ASSERT(aArray.IsEmpty());
return AppendTypedArrayDataTo(aSource, aArray);
if (aSource.IsArrayBuffer()) {
return CopyArrayBufferToArray(aSource.GetAsArrayBuffer(), aArray);
}
if (aSource.IsArrayBufferView()) {
return CopyArrayBufferViewToArray(aSource.GetAsArrayBufferView(), aArray);
}
MOZ_CRASH("Uninitialized union: expected buffer or view");
}
/* static */

View File

@ -23,6 +23,12 @@ class PushUtil final {
PushUtil() = delete;
public:
static bool CopyArrayBufferToArray(const ArrayBuffer& aBuffer,
nsTArray<uint8_t>& aArray);
static bool CopyArrayBufferViewToArray(const ArrayBufferView& aView,
nsTArray<uint8_t>& aArray);
static bool CopyBufferSourceToArray(
const OwningArrayBufferViewOrArrayBuffer& aSource,
nsTArray<uint8_t>& aArray);

View File

@ -546,11 +546,16 @@ void ReportingHeader::GetEndpointForReportInternal(
uint32_t randomNumber = 0;
nsresult rv = randomGenerator->GenerateRandomBytesInto(randomNumber);
uint8_t* buffer;
nsresult rv =
randomGenerator->GenerateRandomBytes(sizeof(randomNumber), &buffer);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
memcpy(&randomNumber, buffer, sizeof(randomNumber));
free(buffer);
totalWeight = randomNumber % totalWeight;
const auto [begin, end] = aGroup.mEndpoints.NonObservingRange();

View File

@ -1030,10 +1030,19 @@ nsresult ExtractBytesFromUSVString(const nsAString& aStr,
nsresult ExtractBytesFromData(
const OwningArrayBufferViewOrArrayBufferOrUSVString& aDataInit,
nsTArray<uint8_t>& aBytes) {
MOZ_ASSERT(aBytes.IsEmpty());
Maybe<bool> result = AppendTypedArrayDataTo(aDataInit, aBytes);
if (result.isSome()) {
return NS_WARN_IF(!result.value()) ? NS_ERROR_OUT_OF_MEMORY : NS_OK;
if (aDataInit.IsArrayBufferView()) {
const ArrayBufferView& view = aDataInit.GetAsArrayBufferView();
if (NS_WARN_IF(!PushUtil::CopyArrayBufferViewToArray(view, aBytes))) {
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
if (aDataInit.IsArrayBuffer()) {
const ArrayBuffer& buffer = aDataInit.GetAsArrayBuffer();
if (NS_WARN_IF(!PushUtil::CopyArrayBufferToArray(buffer, aBytes))) {
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
if (aDataInit.IsUSVString()) {
return ExtractBytesFromUSVString(aDataInit.GetAsUSVString(), aBytes);

View File

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

View File

@ -9,7 +9,6 @@
#include <cstdint>
#include "ErrorList.h"
#include "TypedArray.h"
#include "js/ArrayBuffer.h"
#include "js/ColumnNumber.h" // JS::ColumnNumberZeroOrigin
#include "js/JSON.h"
@ -527,7 +526,9 @@ already_AddRefed<Promise> IOUtils::Write(GlobalObject& aGlobal,
nsCOMPtr<nsIFile> file = new nsLocalFile();
REJECT_IF_INIT_PATH_FAILED(file, aPath, promise);
Maybe<Buffer<uint8_t>> buf = aData.CreateFromData<Buffer<uint8_t>>();
aData.ComputeState();
auto buf =
Buffer<uint8_t>::CopyFrom(Span(aData.Data(), aData.Length()));
if (buf.isNothing()) {
promise->MaybeRejectWithOperationError(
"Out of memory: Could not allocate buffer while writing to file");
@ -542,7 +543,7 @@ already_AddRefed<Promise> IOUtils::Write(GlobalObject& aGlobal,
DispatchAndResolve<uint32_t>(
state->mEventQueue, promise,
[file = std::move(file), buf = buf.extract(),
[file = std::move(file), buf = std::move(*buf),
opts = opts.unwrap()]() { return WriteSync(file, buf, opts); });
});
}
@ -1032,9 +1033,10 @@ already_AddRefed<Promise> IOUtils::SetMacXAttr(GlobalObject& aGlobal,
nsCOMPtr<nsIFile> file = new nsLocalFile();
REJECT_IF_INIT_PATH_FAILED(file, aPath, promise);
aValue.ComputeState();
nsTArray<uint8_t> value;
if (!aValue.AppendDataTo(value)) {
if (!value.AppendElements(aValue.Data(), aValue.Length(), fallible)) {
RejectJSPromise(
promise,
IOError(NS_ERROR_OUT_OF_MEMORY)
@ -2775,51 +2777,51 @@ void SyncReadFile::ReadBytesInto(const Uint8Array& aDestArray,
return aRv.ThrowOperationError("SyncReadFile is closed");
}
aDestArray.ProcessFixedData([&](const Span<uint8_t>& aData) {
auto rangeEnd = CheckedInt64(aOffset) + aData.Length();
if (!rangeEnd.isValid()) {
return aRv.ThrowOperationError("Requested range overflows i64");
}
aDestArray.ComputeState();
if (rangeEnd.value() > mSize) {
auto rangeEnd = CheckedInt64(aOffset) + aDestArray.Length();
if (!rangeEnd.isValid()) {
return aRv.ThrowOperationError("Requested range overflows i64");
}
if (rangeEnd.value() > mSize) {
return aRv.ThrowOperationError(
"Requested range overflows SyncReadFile size");
}
uint32_t readLen{aDestArray.Length()};
if (readLen == 0) {
return;
}
if (nsresult rv = mStream->Seek(PR_SEEK_SET, aOffset); NS_FAILED(rv)) {
return aRv.ThrowOperationError(
FormatErrorMessage(rv, "Could not seek to position %lld", aOffset));
}
Span<char> toRead(reinterpret_cast<char*>(aDestArray.Data()), readLen);
uint32_t totalRead = 0;
while (totalRead != readLen) {
// Read no more than INT32_MAX on each call to mStream->Read, otherwise it
// returns an error.
uint32_t bytesToReadThisChunk =
std::min<uint32_t>(readLen - totalRead, INT32_MAX);
uint32_t bytesRead = 0;
if (nsresult rv =
mStream->Read(toRead.Elements(), bytesToReadThisChunk, &bytesRead);
NS_FAILED(rv)) {
return aRv.ThrowOperationError(FormatErrorMessage(
rv, "Encountered an unexpected error while reading file stream"));
}
if (bytesRead == 0) {
return aRv.ThrowOperationError(
"Requested range overflows SyncReadFile size");
"Reading stopped before the entire array was filled");
}
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);
}
});
totalRead += bytesRead;
toRead = toRead.From(bytesRead);
}
}
void SyncReadFile::Close() { mStream = nullptr; }
@ -2832,9 +2834,10 @@ static nsCString FromUnixString(const IOUtils::UnixString& aString) {
return aString.GetAsUTF8String();
}
if (aString.IsUint8Array()) {
nsCString data;
Unused << aString.GetAsUint8Array().AppendDataTo(data);
return data;
const auto& u8a = aString.GetAsUint8Array();
u8a.ComputeState();
// Cast to deal with char signedness
return nsCString(reinterpret_cast<const char*>(u8a.Data()), u8a.Length());
}
MOZ_CRASH("unreachable");
}

View File

@ -26,13 +26,18 @@ namespace {
template <class T>
bool ReadFloat32Array(T& aDestination, const Float32Array& aSource,
ErrorResult& aRv) {
if (!aSource.CopyDataTo(aDestination)) {
constexpr size_t length = std::extent<T>::value;
aSource.ComputeState();
if (aSource.Length() != length) {
aRv.Throw(NS_ERROR_INVALID_ARG);
// We don't want to MOZ_ASSERT here, as that would cause the
// browser to crash, making it difficult to debug the problem
// in JS code calling this API.
return false;
}
for (size_t i = 0; i < length; i++) {
aDestination[i] = aSource.Data()[i];
}
return true;
}
}; // anonymous namespace

View File

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

View File

@ -2388,31 +2388,37 @@ void WebSocket::Send(Blob& aData, ErrorResult& aRv) {
void WebSocket::Send(const ArrayBuffer& aData, ErrorResult& aRv) {
AssertIsOnTargetThread();
static_assert(
sizeof(std::remove_reference_t<decltype(aData)>::element_type) == 1,
"byte-sized data required");
aData.ComputeState();
nsCString msgString;
if (!aData.AppendDataTo(msgString)) {
static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
uint32_t len = aData.Length();
char* data = reinterpret_cast<char*>(aData.Data());
nsDependentCSubstring msgString;
if (!msgString.Assign(data, len, mozilla::fallible_t())) {
aRv.Throw(NS_ERROR_FILE_TOO_BIG);
return;
}
Send(nullptr, msgString, msgString.Length(), true, aRv);
Send(nullptr, msgString, len, true, aRv);
}
void WebSocket::Send(const ArrayBufferView& aData, ErrorResult& aRv) {
AssertIsOnTargetThread();
static_assert(
sizeof(std::remove_reference_t<decltype(aData)>::element_type) == 1,
"byte-sized data required");
aData.ComputeState();
nsCString msgString;
if (!aData.AppendDataTo(msgString)) {
static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
uint32_t len = aData.Length();
char* data = reinterpret_cast<char*>(aData.Data());
nsDependentCSubstring msgString;
if (!msgString.Assign(data, len, mozilla::fallible_t())) {
aRv.Throw(NS_ERROR_FILE_TOO_BIG);
return;
}
Send(nullptr, msgString, msgString.Length(), true, aRv);
Send(nullptr, msgString, len, true, aRv);
}
void WebSocket::Send(nsIInputStream* aMsgStream, const nsACString& aMsgString,

View File

@ -279,8 +279,19 @@ already_AddRefed<Promise> OutgoingDatagramStreamAlgorithms::WriteCallback(
// Step 3: Let datagrams be transport.[[Datagrams]].
// (mDatagrams is transport.[[Datagrams]])
nsTArray<uint8_t> data;
Unused << AppendTypedArrayDataTo(arrayBuffer, data);
// This is a duplicate of dom/encoding/TextDecoderStream.cpp#51-69
// PeterV will deal with that when he lands his patch for TypedArrays
auto data = [&arrayBuffer]() {
if (arrayBuffer.IsArrayBuffer()) {
const ArrayBuffer& buffer = arrayBuffer.GetAsArrayBuffer();
buffer.ComputeState();
return Span{buffer.Data(), buffer.Length()};
}
MOZ_ASSERT(arrayBuffer.IsArrayBufferView());
const ArrayBufferView& buffer = arrayBuffer.GetAsArrayBufferView();
buffer.ComputeState();
return Span{buffer.Data(), buffer.Length()};
}();
// Step 4: If datagrams.[[OutgoingMaxDatagramSize]] is less than datas
// [[ByteLength]], return a promise resolved with undefined.
@ -298,9 +309,10 @@ already_AddRefed<Promise> OutgoingDatagramStreamAlgorithms::WriteCallback(
// We pass along the datagram to the parent immediately.
// The OutgoingDatagramsQueue lives there, and steps 6-9 generally are
// implemented there
LOG(("Sending Datagram, size = %zu", data.Length()));
nsTArray<uint8_t> array(data);
LOG(("Sending Datagram, size = %zu", array.Length()));
mChild->SendOutgoingDatagram(
std::move(data), now,
array, now,
[promise](nsresult&&) {
// XXX result
LOG(("Datagram was sent"));
@ -319,7 +331,7 @@ already_AddRefed<Promise> OutgoingDatagramStreamAlgorithms::WriteCallback(
// We should be guaranteed that we don't get called again until the
// promise is resolved.
MOZ_ASSERT(mWaitConnect == nullptr);
mWaitConnect.reset(new DatagramEntry(std::move(data), now));
mWaitConnect.reset(new DatagramEntry(data, now));
mWaitConnectPromise = promise;
}

View File

@ -1045,25 +1045,16 @@ class JS_PUBLIC_API AutoRequireNoGC {
class JS_PUBLIC_API AutoAssertNoGC : public AutoRequireNoGC {
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
protected:
JSContext* cx_; // nullptr if inactive.
JSContext* cx_;
public:
// Nullptr here means get the context from TLS. It does not mean inactive
// (though cx_ may end up nullptr, and thus inactive, if TLS has not yet been
// initialized.)
// This gets the context from TLS if it is not passed in.
explicit AutoAssertNoGC(JSContext* cx = nullptr);
AutoAssertNoGC(AutoAssertNoGC&& other) : cx_(other.cx_) {
other.cx_ = nullptr;
}
~AutoAssertNoGC();
void reset();
#else
public:
explicit AutoAssertNoGC(JSContext* cx = nullptr) {}
~AutoAssertNoGC() {}
void reset() {}
#endif
};
@ -1134,15 +1125,11 @@ class JS_PUBLIC_API AutoCheckCannotGC : public AutoAssertNoGC {
# else
AutoCheckCannotGC(const AutoCheckCannotGC& other) : AutoCheckCannotGC() {}
# endif
AutoCheckCannotGC(AutoCheckCannotGC&& other)
: AutoAssertNoGC(std::forward<AutoAssertNoGC>(other)) {}
#else
class JS_PUBLIC_API AutoCheckCannotGC : public AutoRequireNoGC {
public:
explicit AutoCheckCannotGC(JSContext* cx = nullptr) {}
AutoCheckCannotGC(const AutoCheckCannotGC& other) : AutoCheckCannotGC() {}
AutoCheckCannotGC(AutoCheckCannotGC&& other) : AutoCheckCannotGC() {}
void reset() {}
#endif
} JS_HAZ_GC_INVALIDATED JS_HAZ_GC_REF;

View File

@ -275,32 +275,6 @@ namespace JS {
*/
JS_PUBLIC_API bool IsLargeArrayBufferView(JSObject* obj);
/*
* Given an ArrayBuffer or view, prevent the length of the underlying
* ArrayBuffer from changing (with pin=true) until unfrozen (with
* pin=false). Note that some objects (eg SharedArrayBuffers) cannot change
* length to begin with, and are treated as always pinned.
*
* Normally, ArrayBuffers and their views cannot change length, but one way
* currently exists: detaching them. In the future, more will be added with
* GrowableArrayBuffer and ResizableArrayBuffer.
*
* Returns whether the pinned status changed.
*/
JS_PUBLIC_API bool PinArrayBufferOrViewLength(JSObject* obj, bool pin);
/*
* Given an ArrayBuffer or view, make sure its contents are not stored inline
* so that the data is safe for use even if a GC moves the owning object.
*
* Note that this by itself does not make it safe to use the data pointer
* if JS can run or the ArrayBuffer can be detached in any way. Consider using
* this in conjunction with PinArrayBufferOrViewLength, which will cause any
* potentially invalidating operations to fail.
*/
JS_PUBLIC_API bool EnsureNonInlineArrayBufferOrView(JSContext* cx,
JSObject* obj);
namespace detail {
// Map from eg Uint8Clamped -> uint8_t, Uint8 -> uint8_t, or Float64 ->

View File

@ -657,7 +657,6 @@ MSG_DEF(JSMSG_SAME_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected different
MSG_DEF(JSMSG_SHORT_ARRAY_BUFFER_RETURNED, 2, JSEXN_TYPEERR, "expected ArrayBuffer with at least {0} bytes, but species constructor returns ArrayBuffer with {1} bytes")
MSG_DEF(JSMSG_TYPED_ARRAY_BAD_ARGS, 0, JSEXN_TYPEERR, "invalid arguments")
MSG_DEF(JSMSG_TYPED_ARRAY_DETACHED, 0, JSEXN_TYPEERR, "attempting to access detached ArrayBuffer")
MSG_DEF(JSMSG_ARRAYBUFFER_LENGTH_PINNED, 0, JSEXN_RANGEERR, "attempting to change pinned length of ArrayBuffer")
MSG_DEF(JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_BOUNDS, 2, JSEXN_RANGEERR, "start offset of {0}Array should be a multiple of {1}")
MSG_DEF(JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_MISALIGNED, 2, JSEXN_RANGEERR, "buffer length for {0}Array should be a multiple of {1}")
MSG_DEF(JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_LENGTH_BOUNDS, 1, JSEXN_RANGEERR, "size of buffer is too small for {0}Array with byteOffset")

View File

@ -5554,28 +5554,6 @@ static bool DetachArrayBuffer(JSContext* cx, unsigned argc, Value* vp) {
return true;
}
static bool EnsureNonInline(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1) {
JS_ReportErrorASCII(cx, "ensureNonInline() requires a single argument");
return false;
}
if (!args[0].isObject()) {
JS_ReportErrorASCII(cx, "ensureNonInline must be passed an object");
return false;
}
RootedObject obj(cx, &args[0].toObject());
if (!JS::EnsureNonInlineArrayBufferOrView(cx, obj)) {
return false;
}
args.rval().setUndefined();
return true;
}
static bool JSONStringify(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
@ -9388,11 +9366,6 @@ JS_FOR_WASM_FEATURES(WASM_FEATURE)
" 2 - fail during read() hook\n"
" Set the log to null to clear it."),
JS_FN_HELP("ensureNonInline", EnsureNonInline, 1, 0,
"ensureNonInline(view or buffer)",
" Ensure that the memory for the given ArrayBuffer or ArrayBufferView\n"
" is not inline."),
JS_FN_HELP("JSONStringify", JSONStringify, 4, 0,
"JSONStringify(value, behavior)",
" Same as JSON.stringify(value), but allows setting behavior:\n"

View File

@ -9,19 +9,18 @@ function ViewedArrayBufferIfReified(tarray) {
var buf = UnsafeGetReservedSlot(tarray, JS_TYPEDARRAYLAYOUT_BUFFER_SLOT);
assert(
buf === false ||
buf === true ||
buf === null ||
(IsObject(buf) &&
(GuardToArrayBuffer(buf) !== null ||
GuardToSharedArrayBuffer(buf) !== null)),
"unexpected value in buffer slot"
);
return IsObject(buf) ? buf : null;
return buf;
}
function IsDetachedBuffer(buffer) {
// A typed array with a null buffer has never had its buffer exposed,
// and so cannot have become detached.
// A typed array with a null buffer has never had its buffer exposed to
// become detached.
if (buffer === null) {
return false;
}

View File

@ -157,13 +157,10 @@ JS::AutoAssertNoGC::AutoAssertNoGC(JSContext* maybecx) {
}
}
JS::AutoAssertNoGC::~AutoAssertNoGC() { reset(); }
void JS::AutoAssertNoGC::reset() {
JS::AutoAssertNoGC::~AutoAssertNoGC() {
if (cx_) {
MOZ_ASSERT(cx_->inUnsafeRegion > 0);
cx_->inUnsafeRegion--;
cx_ = nullptr;
}
}

View File

@ -1,78 +0,0 @@
const constructors = [
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array ];
function messWith(view, obj) {
view[0] = 1;
view[1] = 1;
view[2] = 2;
view[3] = 3;
ensureNonInline(obj);
assertEq(view[0], 1);
assertEq(view[1], 1);
assertEq(view[2], 2);
assertEq(view[3], 3);
}
function test() {
// Bufferless
for (const ctor of constructors) {
let small = new ctor(4);
messWith(small, small);
let big = new ctor(4000);
messWith(big, big);
}
// With buffer, single view, operate on view
for (const ctor of constructors) {
let ab = new ArrayBuffer(96);
const small = new ctor(ab);
messWith(small, small);
ab = new ArrayBuffer(4000);
const big = new ctor(ab);
messWith(big, big);
}
// With buffer, single view, operate on ArrayBuffer
for (const ctor of constructors) {
let ab = new ArrayBuffer(96);
const small = new ctor(ab);
messWith(small, ab);
ab = new ArrayBuffer(4000);
const big = new ctor(ab);
messWith(big, ab);
}
// With buffer, dual view, operate on view
for (const ctor of constructors) {
let ab = new ArrayBuffer(96);
let view0 = new Uint8Array(ab);
const small = new ctor(ab);
messWith(small, small);
ab = new ArrayBuffer(4000);
view0 = new Uint8Array(ab);
const big = new ctor(ab);
messWith(big, big);
}
// With buffer, dual view, operate on ArrayBuffer
for (const ctor of constructors) {
let ab = new ArrayBuffer(96);
let view0 = new Uint8Array(ab);
const small = new ctor(ab);
messWith(small, ab);
ab = new ArrayBuffer(4000);
view0 = new Uint8Array(ab);
const big = new ctor(ab);
messWith(big, ab);
}
}
test();

View File

@ -6821,8 +6821,8 @@ void MacroAssembler::branchIfHasDetachedArrayBuffer(Register obj, Register temp,
Address(temp, ObjectElements::offsetOfFlags()),
Imm32(ObjectElements::SHARED_MEMORY), &done);
// An ArrayBufferView with a null/true buffer has never had its buffer
// exposed, so nothing can possibly detach it.
// An ArrayBufferView with a null buffer has never had its buffer exposed to
// become detached.
fallibleUnboxObject(Address(obj, ArrayBufferViewObject::bufferOffset()), temp,
&done);

View File

@ -107,62 +107,6 @@ BEGIN_TEST(testTypedArrays) {
return ok;
}
// Test pinning a view's length.
bool TestViewLengthPinning(Handle<JSObject*> view) {
// Pin the length of an inline view. (Fails if shared memory.)
bool isShared = view.as<NativeObject>()->isSharedMemory();
CHECK(JS::PinArrayBufferOrViewLength(view, true) == !isShared);
// Fail to pin an already-pinned length.
CHECK(!JS::PinArrayBufferOrViewLength(view, true));
// Extract an ArrayBuffer. This may cause it to be created, in which case it
// will inherit the pinned status from the view.
bool bufferIsShared;
Rooted<JSObject*> buffer(
cx, JS_GetArrayBufferViewBuffer(cx, view, &bufferIsShared));
CHECK(isShared == bufferIsShared);
// Cannot pin the buffer, since it is already pinned.
CHECK(!JS::PinArrayBufferOrViewLength(buffer, true));
// Should fail to be detached, since its length is pinned.
CHECK(!JS::DetachArrayBuffer(cx, buffer));
CHECK(cx->isExceptionPending());
cx->clearPendingException();
// Unpin (fails if shared memory).
CHECK(JS::PinArrayBufferOrViewLength(view, false) == !isShared);
// Fail to unpin when already unpinned.
CHECK(!JS::PinArrayBufferOrViewLength(view, false));
return true;
}
// Test pinning the length of an ArrayBuffer or SharedArrayBuffer.
bool TestBufferLengthPinning(Handle<JSObject*> buffer) {
// Pin the length of an inline view. (Fails if shared memory.)
bool isShared = !buffer->is<ArrayBufferObject>();
CHECK(JS::PinArrayBufferOrViewLength(buffer, true) == !isShared);
// Fail to pin an already-pinned length.
CHECK(!JS::PinArrayBufferOrViewLength(buffer, true));
// Should fail to be detached, since its length is pinned.
CHECK(!JS::DetachArrayBuffer(cx, buffer));
CHECK(cx->isExceptionPending());
cx->clearPendingException();
// Unpin (fails if shared memory).
CHECK(JS::PinArrayBufferOrViewLength(buffer, false) == !isShared);
// Fail to unpin when already unpinned.
CHECK(!JS::PinArrayBufferOrViewLength(buffer, false));
return true;
}
// Shared memory can only be mapped by a TypedArray by creating the
// TypedArray with a SharedArrayBuffer explicitly, so no tests here.
@ -186,8 +130,6 @@ bool TestPlainTypedArray(JSContext* cx) {
CHECK_EQUAL(JS_GetTypedArrayByteOffset(array), 0u);
CHECK_EQUAL(JS_GetTypedArrayByteLength(array), sizeof(Element) * 7);
TestViewLengthPinning(array);
{
JS::AutoCheckCannotGC nogc;
Element* data;
@ -217,9 +159,6 @@ bool TestArrayFromBuffer(JSContext* cx) {
size_t nbytes = elts * sizeof(Element);
RootedObject buffer(cx, Shared ? JS::NewSharedArrayBuffer(cx, nbytes)
: JS::NewArrayBuffer(cx, nbytes));
TestBufferLengthPinning(buffer);
{
JS::AutoCheckCannotGC nogc;
bool isShared;
@ -246,8 +185,6 @@ bool TestArrayFromBuffer(JSContext* cx) {
CHECK_EQUAL(Shared, isShared);
}
TestViewLengthPinning(array);
{
JS::AutoCheckCannotGC nogc;
Element* data;

View File

@ -681,7 +681,6 @@ void ArrayBufferObject::detach(JSContext* cx,
Handle<ArrayBufferObject*> buffer) {
cx->check(buffer);
MOZ_ASSERT(!buffer->isPreparedForAsmJS());
MOZ_ASSERT(!buffer->isLengthPinned());
// Update all views of the buffer to account for the buffer having been
// detached, and clear the buffer's data and list of views.
@ -1333,10 +1332,6 @@ static void CheckStealPreconditions(Handle<ArrayBufferObject*> buffer,
ArrayBufferObject* ArrayBufferObject::wasmGrowToPagesInPlace(
wasm::IndexType t, Pages newPages, Handle<ArrayBufferObject*> oldBuf,
JSContext* cx) {
if (oldBuf->isLengthPinned()) {
return nullptr;
}
CheckStealPreconditions(oldBuf, cx);
MOZ_ASSERT(oldBuf->isWasm());
@ -1395,9 +1390,6 @@ ArrayBufferObject* ArrayBufferObject::wasmMovingGrowToPages(
JSContext* cx) {
// On failure, do not throw and ensure that the original buffer is
// unmodified and valid.
if (oldBuf->isLengthPinned()) {
return nullptr;
}
// Check that the new pages is within our allowable range. This will
// simultaneously check against the maximum specified in source and our
@ -1833,9 +1825,6 @@ ArrayBufferObject* ArrayBufferObject::createFromNewRawBuffer(
/* static */ uint8_t* ArrayBufferObject::stealMallocedContents(
JSContext* cx, Handle<ArrayBufferObject*> buffer) {
if (buffer->isLengthPinned()) {
return nullptr;
}
CheckStealPreconditions(buffer, cx);
switch (buffer->bufferKind()) {
@ -1891,12 +1880,6 @@ ArrayBufferObject* ArrayBufferObject::createFromNewRawBuffer(
/* static */ ArrayBufferObject::BufferContents
ArrayBufferObject::extractStructuredCloneContents(
JSContext* cx, Handle<ArrayBufferObject*> buffer) {
if (buffer->isLengthPinned()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_ARRAYBUFFER_LENGTH_PINNED);
return BufferContents::createFailed();
}
CheckStealPreconditions(buffer, cx);
BufferContents contents = buffer->contents();
@ -1949,55 +1932,6 @@ ArrayBufferObject::extractStructuredCloneContents(
return BufferContents::createFailed();
}
/* static */
bool ArrayBufferObject::ensureNonInline(JSContext* cx,
Handle<ArrayBufferObject*> buffer) {
if (buffer->isDetached()) {
return true;
}
if (buffer->isLengthPinned()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_ARRAYBUFFER_LENGTH_PINNED);
return false;
}
MOZ_ASSERT(!buffer->isPreparedForAsmJS());
BufferContents inlineContents = buffer->contents();
if (inlineContents.kind() != INLINE_DATA) {
return true;
}
size_t nbytes = buffer->byteLength();
ArrayBufferContents copy = NewCopiedBufferContents(cx, buffer);
if (!copy) {
return false;
}
BufferContents outOfLineContents =
BufferContents::createMalloced(copy.release());
buffer->setDataPointer(outOfLineContents);
AddCellMemory(buffer, nbytes, MemoryUse::ArrayBufferContents);
if (!buffer->firstView()) {
return true; // No views! Easy!
}
buffer->firstView()->as<ArrayBufferViewObject>().notifyBufferMoved(
inlineContents.data(), outOfLineContents.data());
auto& innerViews = ObjectRealm::get(buffer).innerViews.get();
if (InnerViewTable::ViewVector* views =
innerViews.maybeViewsUnbarriered(buffer)) {
for (JSObject* view : *views) {
view->as<ArrayBufferViewObject>().notifyBufferMoved(
inlineContents.data(), outOfLineContents.data());
}
}
return true;
}
/* static */
void ArrayBufferObject::addSizeOfExcludingThis(
JSObject* obj, mozilla::MallocSizeOf mallocSizeOf, JS::ClassInfo* info,
@ -2258,11 +2192,6 @@ JS_PUBLIC_API bool JS::DetachArrayBuffer(JSContext* cx, HandleObject obj) {
JSMSG_WASM_NO_TRANSFER);
return false;
}
if (unwrappedBuffer->isLengthPinned()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_ARRAYBUFFER_LENGTH_PINNED);
return false;
}
AutoRealm ar(cx, unwrappedBuffer);
ArrayBufferObject::detach(cx, unwrappedBuffer);

View File

@ -124,8 +124,6 @@ class ArrayBufferObjectMaybeShared : public NativeObject {
inline bool isDetached() const;
inline SharedMem<uint8_t*> dataPointerEither();
inline bool pinLength(bool pin);
// WebAssembly support:
// Note: the eventual goal is to remove this from ArrayBuffer and have
// (Shared)ArrayBuffers alias memory owned by some wasm::Memory object.
@ -238,11 +236,6 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared {
// INLINE_DATA buffer is used with asm.js, it's silently rewritten into a
// MALLOCED buffer which *can* be prepared.)
FOR_ASMJS = 0b10'0000,
// The length is temporarily pinned, so it should not be detached. In the
// future, this will also prevent GrowableArrayBuffer/ResizeableArrayBuffer
// from modifying the length while this is set.
PINNED_LENGTH = 0b100'0000
};
static_assert(JS_ARRAYBUFFER_DETACHED_FLAG == DETACHED,
@ -407,17 +400,6 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared {
bool addView(JSContext* cx, ArrayBufferViewObject* view);
// Pin or unpin the length. Returns whether pinned status was changed.
bool pinLength(bool pin) {
if (bool(flags() & PINNED_LENGTH) == pin) {
return false;
}
setFlags(flags() ^ PINNED_LENGTH);
return true;
}
static bool ensureNonInline(JSContext* cx, Handle<ArrayBufferObject*> buffer);
// Detach this buffer from its original memory. (This necessarily makes
// views of this buffer unusable for modifying that original memory.)
static void detach(JSContext* cx, Handle<ArrayBufferObject*> buffer);
@ -470,7 +452,6 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared {
bool isExternal() const { return bufferKind() == EXTERNAL; }
bool isDetached() const { return flags() & DETACHED; }
bool isLengthPinned() const { return flags() & PINNED_LENGTH; }
bool isPreparedForAsmJS() const { return flags() & FOR_ASMJS; }
// Only WASM and asm.js buffers have a non-undefined [[ArrayBufferDetachKey]].
@ -516,10 +497,7 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared {
uint32_t flags() const;
void setFlags(uint32_t flags);
void setIsDetached() {
MOZ_ASSERT(!(flags() & PINNED_LENGTH));
setFlags(flags() | DETACHED);
}
void setIsDetached() { setFlags(flags() | DETACHED); }
void setIsPreparedForAsmJS() {
MOZ_ASSERT(!isWasm());
MOZ_ASSERT(!hasUserOwnedData());
@ -536,13 +514,6 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared {
}
};
inline bool ArrayBufferObjectMaybeShared::pinLength(bool pin) {
if (is<ArrayBufferObject>()) {
return as<ArrayBufferObject>().pinLength(pin);
}
return false; // Cannot pin or unpin shared array buffers.
}
// Create a buffer for a wasm memory, whose type is determined by
// memory.indexType().
ArrayBufferObjectMaybeShared* CreateWasmBuffer(JSContext* cx,

View File

@ -8,7 +8,6 @@
#include "builtin/DataViewObject.h"
#include "gc/Nursery.h"
#include "js/ErrorReport.h"
#include "js/experimental/TypedData.h" // JS_GetArrayBufferView{Data,Buffer,Length,ByteOffset}, JS_GetObjectAsArrayBufferView, JS_IsArrayBufferViewObject
#include "js/SharedArrayBuffer.h"
#include "vm/Compartment.h"
@ -40,9 +39,11 @@ void ArrayBufferViewObject::trace(JSTracer* trc, JSObject* obj) {
// The data may or may not be inline with the buffer. The buffer can only
// move during a compacting GC, in which case its objectMoved hook has
// already updated the buffer's data pointer.
view->notifyBufferMoved(
static_cast<uint8_t*>(view->dataPointerEither_()) - offset,
buffer->dataPointer());
void* oldData = view->dataPointerEither_();
void* data = buffer->dataPointer() + offset;
if (data != oldData) {
view->getFixedSlotRef(DATA_SLOT).unbarrieredSet(PrivateValue(data));
}
}
}
}
@ -55,40 +56,14 @@ bool JSObject::is<js::ArrayBufferViewObject>() const {
void ArrayBufferViewObject::notifyBufferDetached() {
MOZ_ASSERT(!isSharedMemory());
MOZ_ASSERT(hasBuffer());
MOZ_ASSERT(!bufferUnshared()->isLengthPinned());
setFixedSlot(LENGTH_SLOT, PrivateValue(size_t(0)));
setFixedSlot(BYTEOFFSET_SLOT, PrivateValue(size_t(0)));
setFixedSlot(DATA_SLOT, UndefinedValue());
}
void ArrayBufferViewObject::notifyBufferMoved(uint8_t* srcBufStart,
uint8_t* dstBufStart) {
MOZ_ASSERT(!isSharedMemory());
MOZ_ASSERT(hasBuffer());
if (srcBufStart != dstBufStart) {
void* data = dstBufStart + byteOffset();
getFixedSlotRef(DATA_SLOT).unbarrieredSet(PrivateValue(data));
}
}
/* static */
bool ArrayBufferViewObject::ensureNonInline(
JSContext* cx, Handle<ArrayBufferViewObject*> view) {
MOZ_ASSERT(!view->isSharedMemory());
// Create an ArrayBuffer for the data if it was in the view.
ArrayBufferObjectMaybeShared* buffer = ensureBufferObject(cx, view);
if (!buffer) {
return false;
}
Rooted<ArrayBufferObject*> unsharedBuffer(cx,
&buffer->as<ArrayBufferObject>());
return ArrayBufferObject::ensureNonInline(cx, unsharedBuffer);
}
/* static */
ArrayBufferObjectMaybeShared* ArrayBufferViewObject::ensureBufferObject(
ArrayBufferObjectMaybeShared* ArrayBufferViewObject::bufferObject(
JSContext* cx, Handle<ArrayBufferViewObject*> thisObject) {
if (thisObject->is<TypedArrayObject>()) {
Rooted<TypedArrayObject*> typedArray(cx,
@ -123,12 +98,7 @@ bool ArrayBufferViewObject::init(JSContext* cx,
initFixedSlot(BYTEOFFSET_SLOT, PrivateValue(byteOffset));
initFixedSlot(LENGTH_SLOT, PrivateValue(length));
if (buffer) {
initFixedSlot(BUFFER_SLOT, ObjectValue(*buffer));
} else {
MOZ_ASSERT(!isSharedMemory());
initFixedSlot(BUFFER_SLOT, JS::FalseValue());
}
initFixedSlot(BUFFER_SLOT, ObjectOrNullValue(buffer));
if (buffer) {
SharedMem<uint8_t*> ptr = buffer->dataPointerEither();
@ -246,8 +216,7 @@ JS_PUBLIC_API JSObject* JS_GetArrayBufferViewBuffer(JSContext* cx,
ArrayBufferObjectMaybeShared* unwrappedBuffer;
{
AutoRealm ar(cx, unwrappedView);
unwrappedBuffer =
ArrayBufferViewObject::ensureBufferObject(cx, unwrappedView);
unwrappedBuffer = ArrayBufferViewObject::bufferObject(cx, unwrappedView);
if (!unwrappedBuffer) {
return nullptr;
}
@ -348,45 +317,3 @@ JS_PUBLIC_API bool JS::IsLargeArrayBufferView(JSObject* obj) {
return false;
#endif
}
JS_PUBLIC_API bool JS::PinArrayBufferOrViewLength(JSObject* obj, bool pin) {
ArrayBufferObjectMaybeShared* buffer =
obj->maybeUnwrapIf<ArrayBufferObjectMaybeShared>();
if (buffer) {
return buffer->pinLength(pin);
}
ArrayBufferViewObject* view = obj->maybeUnwrapAs<ArrayBufferViewObject>();
if (view) {
return view->pinLength(pin);
}
return false;
}
JS_PUBLIC_API bool JS::EnsureNonInlineArrayBufferOrView(JSContext* cx,
JSObject* obj) {
if (obj->is<SharedArrayBufferObject>()) {
// Always locked and out of line.
return true;
}
auto* buffer = obj->maybeUnwrapIf<ArrayBufferObject>();
if (buffer) {
Rooted<ArrayBufferObject*> rootedBuffer(cx, buffer);
return ArrayBufferObject::ensureNonInline(cx, rootedBuffer);
}
auto* view = obj->maybeUnwrapAs<ArrayBufferViewObject>();
if (view) {
if (view->isSharedMemory()) {
// Always locked and out of line.
return true;
}
Rooted<ArrayBufferViewObject*> rootedView(cx, view);
return ArrayBufferViewObject::ensureNonInline(cx, rootedView);
}
JS_ReportErrorASCII(cx, "unhandled type");
return false;
}

View File

@ -24,10 +24,7 @@ namespace js {
class ArrayBufferViewObject : public NativeObject {
public:
// Underlying (Shared)ArrayBufferObject. ObjectValue if there is
// a buffer. Otherwise, the buffer is implicit because the data
// is held inline, and the buffer slot will store the pinned status
// (FalseValue or TrueValue).
// Underlying (Shared)ArrayBufferObject.
static constexpr size_t BUFFER_SLOT = 0;
static_assert(BUFFER_SLOT == JS_TYPEDARRAYLAYOUT_BUFFER_SLOT,
"self-hosted code with burned-in constants must get the "
@ -73,11 +70,10 @@ class ArrayBufferViewObject : public NativeObject {
size_t byteOffset, size_t length,
uint32_t bytesPerElement);
static ArrayBufferObjectMaybeShared* ensureBufferObject(
static ArrayBufferObjectMaybeShared* bufferObject(
JSContext* cx, Handle<ArrayBufferViewObject*> obj);
void notifyBufferDetached();
void notifyBufferMoved(uint8_t* srcBufStart, uint8_t* dstBufStart);
void initDataPointer(SharedMem<uint8_t*> viewData) {
// Install a pointer to the buffer location that corresponds
@ -125,8 +121,7 @@ class ArrayBufferViewObject : public NativeObject {
return &obj->as<SharedArrayBufferObject>();
}
ArrayBufferObjectMaybeShared* bufferEither() const {
JSObject* obj =
bufferValue().isBoolean() ? nullptr : bufferValue().toObjectOrNull();
JSObject* obj = bufferValue().toObjectOrNull();
if (!obj) {
return nullptr;
}
@ -151,43 +146,6 @@ class ArrayBufferViewObject : public NativeObject {
return buffer->isDetached();
}
bool isLengthPinned() const {
Value buffer = bufferValue();
if (buffer.isBoolean()) {
return buffer.toBoolean();
}
if (isSharedMemory()) {
return true;
}
return bufferUnshared()->isLengthPinned();
}
bool pinLength(bool pin) {
if (isSharedMemory()) {
// Always pinned, cannot change.
return false;
}
if (hasBuffer()) {
return bufferUnshared()->pinLength(pin);
}
// No ArrayBuffer (data is inline in the view). bufferValue() is a
// BooleanValue saying whether the length is currently pinned.
MOZ_ASSERT(bufferValue().isBoolean());
bool wasPinned = bufferValue().toBoolean();
if (wasPinned == pin) {
return false;
}
setFixedSlot(BUFFER_SLOT, JS::BooleanValue(pin));
return true;
}
static bool ensureNonInline(JSContext* cx,
JS::Handle<ArrayBufferViewObject*> view);
size_t byteOffset() const {
return size_t(getFixedSlot(BYTEOFFSET_SLOT).toPrivate());
}

View File

@ -122,8 +122,6 @@ bool TypedArrayObject::ensureHasBuffer(JSContext* cx,
return false;
}
buffer->pinLength(tarray->isLengthPinned());
// Attaching the first view to an array buffer is infallible.
MOZ_ALWAYS_TRUE(buffer->addView(cx, tarray));
@ -479,7 +477,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject {
static void initTypedArraySlots(TypedArrayObject* tarray, int32_t len) {
MOZ_ASSERT(len >= 0);
tarray->initFixedSlot(TypedArrayObject::BUFFER_SLOT, JS::FalseValue());
tarray->initFixedSlot(TypedArrayObject::BUFFER_SLOT, NullValue());
tarray->initFixedSlot(TypedArrayObject::LENGTH_SLOT, PrivateValue(len));
tarray->initFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT,
PrivateValue(size_t(0)));

View File

@ -32,16 +32,16 @@ template <typename T>
static void GetDataFrom(const T& aObject, uint8_t*& aBuffer,
uint32_t& aLength) {
MOZ_ASSERT(!aBuffer);
// We need to use malloc here because the gfxUserFontEntry will be calling
// free on it, so the Vector's default AllocPolicy (MallocAllocPolicy) is
// fine.
Maybe<Vector<uint8_t>> buffer =
aObject.template CreateFromData<Vector<uint8_t>>();
if (buffer.isNothing()) {
aObject.ComputeState();
// We use malloc here rather than a FallibleTArray or fallible
// operator new[] since the gfxUserFontEntry will be calling free
// on it.
aBuffer = (uint8_t*)malloc(aObject.Length());
if (!aBuffer) {
return;
}
aLength = buffer->length();
aBuffer = buffer->extractOrCopyRawBuffer();
memcpy((void*)aBuffer, aObject.Data(), aObject.Length());
aLength = aObject.Length();
}
// -- FontFace ---------------------------------------------------------------

View File

@ -9,7 +9,6 @@
#include "js/ArrayBuffer.h" // JS::{GetArrayBuffer{ByteLength,Data},IsArrayBufferObject}
#include "js/RootingAPI.h" // JS::{Handle,Rooted}
#include "js/Value.h" // JS::Value
#include "mozilla/Span.h"
#include "mozilla/UniquePtrExtensions.h"
#include "mozilla/dom/ScriptSettings.h"
@ -19,8 +18,8 @@ NS_IMPL_ISUPPORTS(ArrayBufferInputStream, nsIArrayBufferInputStream,
nsIInputStream);
NS_IMETHODIMP
ArrayBufferInputStream::SetDataFromJS(JS::Handle<JS::Value> aBuffer,
uint64_t aByteOffset, uint64_t aLength) {
ArrayBufferInputStream::SetData(JS::Handle<JS::Value> aBuffer,
uint64_t aByteOffset, uint64_t aLength) {
NS_ASSERT_OWNINGTHREAD(ArrayBufferInputStream);
if (!aBuffer.isObject()) {
@ -40,7 +39,7 @@ ArrayBufferInputStream::SetDataFromJS(JS::Handle<JS::Value> aBuffer,
return NS_ERROR_INVALID_ARG;
}
mArrayBuffer = mozilla::MakeUniqueFallible<uint8_t[]>(bufferLength);
mArrayBuffer = mozilla::MakeUniqueFallible<char[]>(bufferLength);
if (!mArrayBuffer) {
return NS_ERROR_OUT_OF_MEMORY;
}
@ -49,18 +48,12 @@ ArrayBufferInputStream::SetDataFromJS(JS::Handle<JS::Value> aBuffer,
JS::AutoCheckCannotGC nogc;
bool isShared;
uint8_t* src = JS::GetArrayBufferData(arrayBuffer, &isShared, nogc) + offset;
char* src =
(char*)JS::GetArrayBufferData(arrayBuffer, &isShared, nogc) + offset;
memcpy(&mArrayBuffer[0], src, mBufferLength);
return NS_OK;
}
nsresult ArrayBufferInputStream::SetData(mozilla::UniquePtr<uint8_t[]> aBytes,
uint64_t aByteLen) {
mArrayBuffer = std::move(aBytes);
mBufferLength = aByteLen;
return NS_OK;
}
NS_IMETHODIMP
ArrayBufferInputStream::Close() {
mClosed = true;
@ -115,8 +108,8 @@ ArrayBufferInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure,
}
uint32_t written;
nsresult rv = writer(this, closure, (char*)&mArrayBuffer[0] + mPos, *result,
count, &written);
nsresult rv = writer(this, closure, &mArrayBuffer[0] + mPos, *result, count,
&written);
if (NS_FAILED(rv)) {
// InputStreams do not propagate errors to caller.
return NS_OK;

View File

@ -31,7 +31,7 @@ class ArrayBufferInputStream : public nsIArrayBufferInputStream {
private:
virtual ~ArrayBufferInputStream() = default;
mozilla::UniquePtr<uint8_t[]> mArrayBuffer;
mozilla::UniquePtr<char[]> mArrayBuffer;
uint32_t mBufferLength{0};
uint32_t mPos{0};
bool mClosed{false};

View File

@ -5,11 +5,6 @@
#include "nsIInputStream.idl"
%{C++
#include "mozilla/UniquePtr.h"
%}
native Bytes(mozilla::UniquePtr<uint8_t[]>);
/**
* nsIArrayBufferInputStream
*
@ -26,15 +21,5 @@ interface nsIArrayBufferInputStream : nsIInputStream
* @param byteOffset - stream data offset
* @param byteLen - stream data length
*/
[binaryname(SetDataFromJS)]
void setData(in jsval buffer, in uint64_t byteOffset, in uint64_t byteLen);
/**
* SetData - assign data to the input stream.
*
* @param aBytes - stream data
* @param byteLen - stream data length
*/
[noscript, nostdcall, binaryname(SetData)]
void setDataNative(in Bytes bytes, in uint64_t byteLen);
};

View File

@ -4,10 +4,6 @@
#include "nsISupports.idl"
%{C++
#include <type_traits>
%}
/**
* Interface used to generate random data.
*
@ -25,22 +21,4 @@ interface nsIRandomGenerator : nsISupports {
*/
void generateRandomBytes(in unsigned long aLength,
[retval, array, size_is(aLength)] out octet aBuffer);
/**
* Fills aBuffer with random bytes.
*
* @param aBuffer
* A buffer to fill with random bytes.
* @param aLength
* Length of aBuffer.
*/
void generateRandomBytesInto([array, size_is(aLength)] in octet aBuffer,
in unsigned long aLength);
%{C++
template<typename T>
std::enable_if_t<!std::is_pointer_v<T>, nsresult> GenerateRandomBytesInto(T& aResult) {
return GenerateRandomBytesInto(reinterpret_cast<uint8_t*>(&aResult), sizeof(T));
}
%}
};

View File

@ -2134,8 +2134,10 @@ void WebSocketChannel::PrimeNewOutgoingMessage() {
if (!mIsServerSide) {
// Perform the sending mask. Never use a zero mask
do {
uint8_t* buffer;
static_assert(4 == sizeof(mask), "Size of the mask should be equal to 4");
nsresult rv = mRandomGenerator->GenerateRandomBytesInto(mask);
nsresult rv =
mRandomGenerator->GenerateRandomBytes(sizeof(mask), &buffer);
if (NS_FAILED(rv)) {
LOG(
("WebSocketChannel::PrimeNewOutgoingMessage(): "
@ -2144,6 +2146,8 @@ void WebSocketChannel::PrimeNewOutgoingMessage() {
AbortSession(rv);
return;
}
memcpy(&mask, buffer, sizeof(mask));
free(buffer);
} while (!mask);
NetworkEndian::writeUint32(payload - sizeof(uint32_t), mask);
}

View File

@ -9,7 +9,6 @@
#include "pk11pub.h"
#include "prerror.h"
#include "secerr.h"
#include "mozilla/UniquePtrExtensions.h"
NS_IMPL_ISUPPORTS(nsRandomGenerator, nsIRandomGenerator)
@ -18,24 +17,20 @@ nsRandomGenerator::GenerateRandomBytes(uint32_t aLength, uint8_t** aBuffer) {
NS_ENSURE_ARG_POINTER(aBuffer);
*aBuffer = nullptr;
mozilla::UniqueFreePtr<uint8_t> buf(
static_cast<uint8_t*>(moz_xmalloc(aLength)));
nsresult rv = GenerateRandomBytesInto(buf.get(), aLength);
NS_ENSURE_SUCCESS(rv, rv);
*aBuffer = buf.release();
return NS_OK;
}
NS_IMETHODIMP
nsRandomGenerator::GenerateRandomBytesInto(uint8_t* aBuffer, uint32_t aLength) {
NS_ENSURE_ARG_POINTER(aBuffer);
mozilla::UniquePK11SlotInfo slot(PK11_GetInternalSlot());
if (!slot) {
return NS_ERROR_FAILURE;
}
SECStatus srv = PK11_GenerateRandomOnSlot(slot.get(), aBuffer, aLength);
return srv == SECSuccess ? NS_OK : NS_ERROR_FAILURE;
auto buf = static_cast<uint8_t*>(moz_xmalloc(aLength));
SECStatus srv = PK11_GenerateRandomOnSlot(slot.get(), buf, aLength);
if (srv != SECSuccess) {
free(buf);
return NS_ERROR_FAILURE;
}
*aBuffer = buf;
return NS_OK;
}

View File

@ -123,6 +123,18 @@ bool StreamFilter::CheckAlive() {
* Binding methods
*****************************************************************************/
template <typename T>
static inline bool ReadTypedArrayData(nsTArray<uint8_t>& aData, const T& aArray,
ErrorResult& aRv) {
aArray.ComputeState();
if (!aData.SetLength(aArray.Length(), fallible)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return false;
}
memcpy(aData.Elements(), aArray.Data(), aArray.Length());
return true;
}
void StreamFilter::Write(const ArrayBufferOrUint8Array& aData,
ErrorResult& aRv) {
if (!mActor) {
@ -131,12 +143,20 @@ void StreamFilter::Write(const ArrayBufferOrUint8Array& aData,
}
nsTArray<uint8_t> data;
if (!AppendTypedArrayDataTo(aData, data)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
bool ok;
if (aData.IsArrayBuffer()) {
ok = ReadTypedArrayData(data, aData.GetAsArrayBuffer(), aRv);
} else if (aData.IsUint8Array()) {
ok = ReadTypedArrayData(data, aData.GetAsUint8Array(), aRv);
} else {
MOZ_ASSERT_UNREACHABLE("Argument should be ArrayBuffer or Uint8Array");
return;
}
mActor->Write(std::move(data), aRv);
if (ok) {
mActor->Write(std::move(data), aRv);
}
}
StreamFilterStatus StreamFilter::Status() const {

View File

@ -21,11 +21,17 @@ int64_t RelativeTimeline::GetRandomTimelineSeed() {
return mRandomTimelineSeed;
}
rv = randomGenerator->GenerateRandomBytesInto(mRandomTimelineSeed);
uint8_t* buffer = nullptr;
rv = randomGenerator->GenerateRandomBytes(sizeof(mRandomTimelineSeed),
&buffer);
if (NS_WARN_IF(NS_FAILED(rv))) {
mRandomTimelineSeed = rand();
return mRandomTimelineSeed;
}
memcpy(&mRandomTimelineSeed, buffer, sizeof(mRandomTimelineSeed));
MOZ_ASSERT(buffer);
free(buffer);
}
return mRandomTimelineSeed;
}

View File

@ -464,7 +464,7 @@ nsresult nsRFPService::RandomMidpoint(long long aClampedTimeUSec,
}
if (MOZ_UNLIKELY(!sSecretMidpointSeed.compareExchange(nullptr, temp))) {
// Some other thread initted this first, never mind!
free(temp);
delete[] temp;
}
}

View File

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

Some files were not shown because too many files have changed in this diff Show More