Bug 1591579 - XHR uses a ref-counted ArrayBufferBuilder, r=smaug
Differential Revision: https://phabricator.services.mozilla.com/D50919
This commit is contained in:
@@ -229,6 +229,7 @@ XMLHttpRequestMainThread::XMLHttpRequestMainThread()
|
|||||||
mFirstStartRequestSeen(false),
|
mFirstStartRequestSeen(false),
|
||||||
mInLoadProgressEvent(false),
|
mInLoadProgressEvent(false),
|
||||||
mResultJSON(JS::UndefinedValue()),
|
mResultJSON(JS::UndefinedValue()),
|
||||||
|
mArrayBufferBuilder(new ArrayBufferBuilder()),
|
||||||
mResultArrayBuffer(nullptr),
|
mResultArrayBuffer(nullptr),
|
||||||
mIsMappedArrayBuffer(false),
|
mIsMappedArrayBuffer(false),
|
||||||
mXPCOMifier(nullptr),
|
mXPCOMifier(nullptr),
|
||||||
@@ -317,7 +318,7 @@ void XMLHttpRequestMainThread::ResetResponse() {
|
|||||||
mResponseBlob = nullptr;
|
mResponseBlob = nullptr;
|
||||||
mBlobStorage = nullptr;
|
mBlobStorage = nullptr;
|
||||||
mResultArrayBuffer = nullptr;
|
mResultArrayBuffer = nullptr;
|
||||||
mArrayBufferBuilder.reset();
|
mArrayBufferBuilder = new ArrayBufferBuilder();
|
||||||
mResultJSON.setUndefined();
|
mResultJSON.setUndefined();
|
||||||
mLoadTransferred = 0;
|
mLoadTransferred = 0;
|
||||||
mResponseBodyDecodedPos = 0;
|
mResponseBodyDecodedPos = 0;
|
||||||
@@ -351,7 +352,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|||||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XMLHttpRequestMainThread,
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XMLHttpRequestMainThread,
|
||||||
XMLHttpRequestEventTarget)
|
XMLHttpRequestEventTarget)
|
||||||
tmp->mResultArrayBuffer = nullptr;
|
tmp->mResultArrayBuffer = nullptr;
|
||||||
tmp->mArrayBufferBuilder.reset();
|
tmp->mArrayBufferBuilder = nullptr;
|
||||||
tmp->mResultJSON.setUndefined();
|
tmp->mResultJSON.setUndefined();
|
||||||
tmp->mResponseBlobImpl = nullptr;
|
tmp->mResponseBlobImpl = nullptr;
|
||||||
|
|
||||||
@@ -679,7 +680,7 @@ void XMLHttpRequestMainThread::GetResponse(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!mResultArrayBuffer) {
|
if (!mResultArrayBuffer) {
|
||||||
mResultArrayBuffer = mArrayBufferBuilder.getArrayBuffer(aCx);
|
mResultArrayBuffer = mArrayBufferBuilder->GetArrayBuffer(aCx);
|
||||||
if (!mResultArrayBuffer) {
|
if (!mResultArrayBuffer) {
|
||||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||||
return;
|
return;
|
||||||
@@ -1512,11 +1513,11 @@ nsresult XMLHttpRequestMainThread::StreamReaderFunc(
|
|||||||
!xmlHttpRequest->mIsMappedArrayBuffer) {
|
!xmlHttpRequest->mIsMappedArrayBuffer) {
|
||||||
// get the initial capacity to something reasonable to avoid a bunch of
|
// get the initial capacity to something reasonable to avoid a bunch of
|
||||||
// reallocs right at the start
|
// reallocs right at the start
|
||||||
if (xmlHttpRequest->mArrayBufferBuilder.capacity() == 0)
|
if (xmlHttpRequest->mArrayBufferBuilder->Capacity() == 0)
|
||||||
xmlHttpRequest->mArrayBufferBuilder.setCapacity(
|
xmlHttpRequest->mArrayBufferBuilder->SetCapacity(
|
||||||
std::max(count, XML_HTTP_REQUEST_ARRAYBUFFER_MIN_SIZE));
|
std::max(count, XML_HTTP_REQUEST_ARRAYBUFFER_MIN_SIZE));
|
||||||
|
|
||||||
if (NS_WARN_IF(!xmlHttpRequest->mArrayBufferBuilder.append(
|
if (NS_WARN_IF(!xmlHttpRequest->mArrayBufferBuilder->Append(
|
||||||
reinterpret_cast<const uint8_t*>(fromRawSegment), count,
|
reinterpret_cast<const uint8_t*>(fromRawSegment), count,
|
||||||
XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH))) {
|
XML_HTTP_REQUEST_ARRAYBUFFER_MAX_GROWTH))) {
|
||||||
return NS_ERROR_OUT_OF_MEMORY;
|
return NS_ERROR_OUT_OF_MEMORY;
|
||||||
@@ -1861,7 +1862,7 @@ XMLHttpRequestMainThread::OnStartRequest(nsIRequest* request) {
|
|||||||
if (!jarFile) {
|
if (!jarFile) {
|
||||||
mIsMappedArrayBuffer = false;
|
mIsMappedArrayBuffer = false;
|
||||||
} else {
|
} else {
|
||||||
rv = mArrayBufferBuilder.mapToFileInPackage(file, jarFile);
|
rv = mArrayBufferBuilder->MapToFileInPackage(file, jarFile);
|
||||||
// This can happen legitimately if there are compressed files
|
// This can happen legitimately if there are compressed files
|
||||||
// in the jarFile. See bug #1357219. No need to warn on the error.
|
// in the jarFile. See bug #1357219. No need to warn on the error.
|
||||||
if (NS_FAILED(rv)) {
|
if (NS_FAILED(rv)) {
|
||||||
@@ -1881,7 +1882,7 @@ XMLHttpRequestMainThread::OnStartRequest(nsIRequest* request) {
|
|||||||
rv = channel->GetContentLength(&contentLength);
|
rv = channel->GetContentLength(&contentLength);
|
||||||
if (NS_SUCCEEDED(rv) && contentLength > 0 &&
|
if (NS_SUCCEEDED(rv) && contentLength > 0 &&
|
||||||
contentLength < XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE) {
|
contentLength < XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE) {
|
||||||
mArrayBufferBuilder.setCapacity(static_cast<int32_t>(contentLength));
|
mArrayBufferBuilder->SetCapacity(static_cast<int32_t>(contentLength));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2154,7 +2155,7 @@ XMLHttpRequestMainThread::OnStopRequest(nsIRequest* request, nsresult status) {
|
|||||||
mResponseType == XMLHttpRequestResponseType::Arraybuffer) {
|
mResponseType == XMLHttpRequestResponseType::Arraybuffer) {
|
||||||
// set the capacity down to the actual length, to realloc back
|
// set the capacity down to the actual length, to realloc back
|
||||||
// down to the actual size
|
// down to the actual size
|
||||||
if (!mArrayBufferBuilder.setCapacity(mArrayBufferBuilder.length())) {
|
if (!mArrayBufferBuilder->SetCapacity(mArrayBufferBuilder->Length())) {
|
||||||
// this should never happen!
|
// this should never happen!
|
||||||
status = NS_ERROR_UNEXPECTED;
|
status = NS_ERROR_UNEXPECTED;
|
||||||
}
|
}
|
||||||
@@ -3651,11 +3652,13 @@ nsXMLHttpRequestXPCOMifier::GetInterface(const nsIID& aIID, void** aResult) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ArrayBufferBuilder::ArrayBufferBuilder()
|
ArrayBufferBuilder::ArrayBufferBuilder()
|
||||||
: mDataPtr(nullptr), mCapacity(0), mLength(0), mMapPtr(nullptr) {}
|
: mMutex("ArrayBufferBuilder"),
|
||||||
|
mDataPtr(nullptr),
|
||||||
|
mCapacity(0),
|
||||||
|
mLength(0),
|
||||||
|
mMapPtr(nullptr) {}
|
||||||
|
|
||||||
ArrayBufferBuilder::~ArrayBufferBuilder() { reset(); }
|
ArrayBufferBuilder::~ArrayBufferBuilder() {
|
||||||
|
|
||||||
void ArrayBufferBuilder::reset() {
|
|
||||||
if (mDataPtr) {
|
if (mDataPtr) {
|
||||||
JS_free(nullptr, mDataPtr);
|
JS_free(nullptr, mDataPtr);
|
||||||
}
|
}
|
||||||
@@ -3669,7 +3672,13 @@ void ArrayBufferBuilder::reset() {
|
|||||||
mCapacity = mLength = 0;
|
mCapacity = mLength = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ArrayBufferBuilder::setCapacity(uint32_t aNewCap) {
|
bool ArrayBufferBuilder::SetCapacity(uint32_t aNewCap) {
|
||||||
|
MutexAutoLock lock(mMutex);
|
||||||
|
return SetCapacityInternal(aNewCap, lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ArrayBufferBuilder::SetCapacityInternal(
|
||||||
|
uint32_t aNewCap, const MutexAutoLock& aProofOfLock) {
|
||||||
MOZ_ASSERT(!mMapPtr);
|
MOZ_ASSERT(!mMapPtr);
|
||||||
|
|
||||||
// To ensure that realloc won't free mDataPtr, use a size of 1
|
// To ensure that realloc won't free mDataPtr, use a size of 1
|
||||||
@@ -3693,8 +3702,9 @@ bool ArrayBufferBuilder::setCapacity(uint32_t aNewCap) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ArrayBufferBuilder::append(const uint8_t* aNewData, uint32_t aDataLen,
|
bool ArrayBufferBuilder::Append(const uint8_t* aNewData, uint32_t aDataLen,
|
||||||
uint32_t aMaxGrowth) {
|
uint32_t aMaxGrowth) {
|
||||||
|
MutexAutoLock lock(mMutex);
|
||||||
MOZ_ASSERT(!mMapPtr);
|
MOZ_ASSERT(!mMapPtr);
|
||||||
|
|
||||||
CheckedUint32 neededCapacity = mLength;
|
CheckedUint32 neededCapacity = mLength;
|
||||||
@@ -3720,14 +3730,14 @@ bool ArrayBufferBuilder::append(const uint8_t* aNewData, uint32_t aDataLen,
|
|||||||
newcap = neededCapacity;
|
newcap = neededCapacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!setCapacity(newcap.value())) {
|
if (!SetCapacityInternal(newcap.value(), lock)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assert that the region isn't overlapping so we can memcpy.
|
// Assert that the region isn't overlapping so we can memcpy.
|
||||||
MOZ_ASSERT(
|
MOZ_ASSERT(
|
||||||
!areOverlappingRegions(aNewData, aDataLen, mDataPtr + mLength, aDataLen));
|
!AreOverlappingRegions(aNewData, aDataLen, mDataPtr + mLength, aDataLen));
|
||||||
|
|
||||||
memcpy(mDataPtr + mLength, aNewData, aDataLen);
|
memcpy(mDataPtr + mLength, aNewData, aDataLen);
|
||||||
mLength += aDataLen;
|
mLength += aDataLen;
|
||||||
@@ -3735,7 +3745,19 @@ bool ArrayBufferBuilder::append(const uint8_t* aNewData, uint32_t aDataLen,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
JSObject* ArrayBufferBuilder::getArrayBuffer(JSContext* aCx) {
|
uint32_t ArrayBufferBuilder::Length() {
|
||||||
|
MutexAutoLock lock(mMutex);
|
||||||
|
return mLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ArrayBufferBuilder::Capacity() {
|
||||||
|
MutexAutoLock lock(mMutex);
|
||||||
|
return mCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSObject* ArrayBufferBuilder::GetArrayBuffer(JSContext* aCx) {
|
||||||
|
MutexAutoLock lock(mMutex);
|
||||||
|
|
||||||
if (mMapPtr) {
|
if (mMapPtr) {
|
||||||
JSObject* obj = JS::NewMappedArrayBufferWithContents(aCx, mLength, mMapPtr);
|
JSObject* obj = JS::NewMappedArrayBufferWithContents(aCx, mLength, mMapPtr);
|
||||||
if (!obj) {
|
if (!obj) {
|
||||||
@@ -3751,7 +3773,7 @@ JSObject* ArrayBufferBuilder::getArrayBuffer(JSContext* aCx) {
|
|||||||
// we need to check for mLength == 0, because nothing may have been
|
// we need to check for mLength == 0, because nothing may have been
|
||||||
// added
|
// added
|
||||||
if (mCapacity > mLength || mLength == 0) {
|
if (mCapacity > mLength || mLength == 0) {
|
||||||
if (!setCapacity(mLength)) {
|
if (!SetCapacityInternal(mLength, lock)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3765,8 +3787,10 @@ JSObject* ArrayBufferBuilder::getArrayBuffer(JSContext* aCx) {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult ArrayBufferBuilder::mapToFileInPackage(const nsCString& aFile,
|
nsresult ArrayBufferBuilder::MapToFileInPackage(const nsCString& aFile,
|
||||||
nsIFile* aJarFile) {
|
nsIFile* aJarFile) {
|
||||||
|
MutexAutoLock lock(mMutex);
|
||||||
|
|
||||||
nsresult rv;
|
nsresult rv;
|
||||||
|
|
||||||
// Open Jar file to get related attributes of target file.
|
// Open Jar file to get related attributes of target file.
|
||||||
@@ -3801,7 +3825,7 @@ nsresult ArrayBufferBuilder::mapToFileInPackage(const nsCString& aFile,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* static */
|
/* static */
|
||||||
bool ArrayBufferBuilder::areOverlappingRegions(const uint8_t* aStart1,
|
bool ArrayBufferBuilder::AreOverlappingRegions(const uint8_t* aStart1,
|
||||||
uint32_t aLength1,
|
uint32_t aLength1,
|
||||||
const uint8_t* aStart2,
|
const uint8_t* aStart2,
|
||||||
uint32_t aLength2) {
|
uint32_t aLength2) {
|
||||||
|
|||||||
@@ -73,24 +73,17 @@ struct OriginAttributesDictionary;
|
|||||||
// before creating the ArrayBuffer itself. Will do doubling
|
// before creating the ArrayBuffer itself. Will do doubling
|
||||||
// based reallocation, up to an optional maximum growth given.
|
// based reallocation, up to an optional maximum growth given.
|
||||||
//
|
//
|
||||||
// When all the data has been appended, call getArrayBuffer,
|
// When all the data has been appended, call GetArrayBuffer,
|
||||||
// passing in the JSContext* for which the ArrayBuffer object
|
// passing in the JSContext* for which the ArrayBuffer object
|
||||||
// is to be created. This also implicitly resets the builder,
|
// is to be created. This also implicitly resets the builder.
|
||||||
// or it can be reset explicitly at any point by calling reset().
|
|
||||||
class ArrayBufferBuilder {
|
class ArrayBufferBuilder {
|
||||||
uint8_t* mDataPtr;
|
|
||||||
uint32_t mCapacity;
|
|
||||||
uint32_t mLength;
|
|
||||||
void* mMapPtr;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ArrayBufferBuilder);
|
||||||
|
|
||||||
ArrayBufferBuilder();
|
ArrayBufferBuilder();
|
||||||
~ArrayBufferBuilder();
|
|
||||||
|
|
||||||
void reset();
|
// Will truncate if aNewCap is < Length().
|
||||||
|
bool SetCapacity(uint32_t aNewCap);
|
||||||
// Will truncate if aNewCap is < length().
|
|
||||||
bool setCapacity(uint32_t aNewCap);
|
|
||||||
|
|
||||||
// Append aDataLen bytes from data to the current buffer. If we
|
// Append aDataLen bytes from data to the current buffer. If we
|
||||||
// need to grow the buffer, grow by doubling the size up to a
|
// need to grow the buffer, grow by doubling the size up to a
|
||||||
@@ -99,13 +92,13 @@ class ArrayBufferBuilder {
|
|||||||
//
|
//
|
||||||
// The data parameter must not overlap with anything beyond the
|
// The data parameter must not overlap with anything beyond the
|
||||||
// builder's current valid contents [0..length)
|
// builder's current valid contents [0..length)
|
||||||
bool append(const uint8_t* aNewData, uint32_t aDataLen,
|
bool Append(const uint8_t* aNewData, uint32_t aDataLen,
|
||||||
uint32_t aMaxGrowth = 0);
|
uint32_t aMaxGrowth = 0);
|
||||||
|
|
||||||
uint32_t length() { return mLength; }
|
uint32_t Length();
|
||||||
uint32_t capacity() { return mCapacity; }
|
uint32_t Capacity();
|
||||||
|
|
||||||
JSObject* getArrayBuffer(JSContext* aCx);
|
JSObject* GetArrayBuffer(JSContext* aCx);
|
||||||
|
|
||||||
// Memory mapping to starting position of file(aFile) in the zip
|
// Memory mapping to starting position of file(aFile) in the zip
|
||||||
// package(aJarFile).
|
// package(aJarFile).
|
||||||
@@ -113,11 +106,27 @@ class ArrayBufferBuilder {
|
|||||||
// The file in the zip package has to be uncompressed and the starting
|
// The file in the zip package has to be uncompressed and the starting
|
||||||
// position of the file must be aligned according to array buffer settings
|
// position of the file must be aligned according to array buffer settings
|
||||||
// in JS engine.
|
// in JS engine.
|
||||||
nsresult mapToFileInPackage(const nsCString& aFile, nsIFile* aJarFile);
|
nsresult MapToFileInPackage(const nsCString& aFile, nsIFile* aJarFile);
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
static bool areOverlappingRegions(const uint8_t* aStart1, uint32_t aLength1,
|
~ArrayBufferBuilder();
|
||||||
|
|
||||||
|
ArrayBufferBuilder(const ArrayBufferBuilder&) = delete;
|
||||||
|
ArrayBufferBuilder& operator=(const ArrayBufferBuilder&) = delete;
|
||||||
|
ArrayBufferBuilder& operator=(const ArrayBufferBuilder&&) = delete;
|
||||||
|
|
||||||
|
bool SetCapacityInternal(uint32_t aNewCap, const MutexAutoLock& aProofOfLock);
|
||||||
|
|
||||||
|
static bool AreOverlappingRegions(const uint8_t* aStart1, uint32_t aLength1,
|
||||||
const uint8_t* aStart2, uint32_t aLength2);
|
const uint8_t* aStart2, uint32_t aLength2);
|
||||||
|
|
||||||
|
Mutex mMutex;
|
||||||
|
|
||||||
|
// All of these are protected by mMutex.
|
||||||
|
uint8_t* mDataPtr;
|
||||||
|
uint32_t mCapacity;
|
||||||
|
uint32_t mLength;
|
||||||
|
void* mMapPtr;
|
||||||
};
|
};
|
||||||
|
|
||||||
class nsXMLHttpRequestXPCOMifier;
|
class nsXMLHttpRequestXPCOMifier;
|
||||||
@@ -718,7 +727,7 @@ class XMLHttpRequestMainThread final : public XMLHttpRequest,
|
|||||||
|
|
||||||
JS::Heap<JS::Value> mResultJSON;
|
JS::Heap<JS::Value> mResultJSON;
|
||||||
|
|
||||||
ArrayBufferBuilder mArrayBufferBuilder;
|
RefPtr<ArrayBufferBuilder> mArrayBufferBuilder;
|
||||||
JS::Heap<JSObject*> mResultArrayBuffer;
|
JS::Heap<JSObject*> mResultArrayBuffer;
|
||||||
bool mIsMappedArrayBuffer;
|
bool mIsMappedArrayBuffer;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user