/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * 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/. */
if (MOZ_UNLIKELY(!HasMore())) {
MOZ_ASSERT_UNREACHABLE("Should not advance a completed iterator"); return COMPLETE;
}
// The range of data [mOffset, mOffset + mNextReadLength) has just been read // by the caller (or at least they don't have any interest in it), so consume // that data.
MOZ_ASSERT(mData.mIterating.mNextReadLength <=
mData.mIterating.mAvailableLength);
mData.mIterating.mOffset += mData.mIterating.mNextReadLength;
mData.mIterating.mAvailableLength -= mData.mIterating.mNextReadLength;
// An iterator can have a limit imposed on it to read only a subset of a // source buffer. If it is present, we need to mimic the same behaviour as // the owning SourceBuffer. if (MOZ_UNLIKELY(mRemainderToRead != SIZE_MAX)) {
MOZ_ASSERT(mData.mIterating.mNextReadLength <= mRemainderToRead);
mRemainderToRead -= mData.mIterating.mNextReadLength;
if (MOZ_UNLIKELY(aRequestedBytes > mRemainderToRead)) {
aRequestedBytes = mRemainderToRead;
}
}
mData.mIterating.mNextReadLength = 0;
if (MOZ_LIKELY(mState == READY)) { // If the caller wants zero bytes of data, that's easy enough; we just // configured ourselves for a zero-byte read above! In theory we could do // this even in the START state, but it's not important for performance and // breaking the ability of callers to assert that the pointer returned by // Data() is non-null doesn't seem worth it. if (aRequestedBytes == 0) {
MOZ_ASSERT(mData.mIterating.mNextReadLength == 0); return READY;
}
// Try to satisfy the request out of our local buffer. This is potentially // much faster than requesting data from our owning SourceBuffer because we // don't have to take the lock. Note that if we have anything at all in our // local buffer, we use it to satisfy the request; @aRequestedBytes is just // the *maximum* number of bytes we can return. if (mData.mIterating.mAvailableLength > 0) { return AdvanceFromLocalBuffer(aRequestedBytes);
}
}
// Our local buffer is empty, so we'll have to request data from our owning // SourceBuffer. return mOwner->AdvanceIteratorOrScheduleResume(*this, aRequestedBytes,
aConsumer);
}
if (MOZ_UNLIKELY(!aChunk)) { return NS_ERROR_OUT_OF_MEMORY;
}
if (MOZ_UNLIKELY(aChunk->AllocationFailed())) { return NS_ERROR_OUT_OF_MEMORY;
}
if (MOZ_UNLIKELY(!mChunks.AppendElement(std::move(*aChunk), fallible))) { return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
Maybe<SourceBuffer::Chunk> SourceBuffer::CreateChunk(
size_t aCapacity, size_t aExistingCapacity /* = 0 */, bool aRoundUp /* = true */) { if (MOZ_UNLIKELY(aCapacity == 0)) {
MOZ_ASSERT_UNREACHABLE("Appending a chunk of zero size?"); return Nothing();
}
// Round up if requested.
size_t finalCapacity = aRoundUp ? RoundedUpCapacity(aCapacity) : aCapacity;
// Use the size of the SurfaceCache as an additional heuristic to avoid // allocating huge buffers. Generally images do not get smaller when decoded, // so if we could store the source data in the SurfaceCache, we assume that // there's no way we'll be able to store the decoded version. if (MOZ_UNLIKELY(!SurfaceCache::CanHold(finalCapacity + aExistingCapacity))) {
NS_WARNING( "SourceBuffer refused to create chunk too large for SurfaceCache"); return Nothing();
}
MOZ_ASSERT(mConsumerCount == 0, "Should have no consumers here");
MOZ_ASSERT(mWaitingConsumers.Length() == 0, "Shouldn't have waiters");
MOZ_ASSERT(mStatus, "Should be complete here");
// If we've tried to compact once, don't attempt again. if (mCompacted) { return NS_OK;
}
mCompacted = true;
// Compact our waiting consumers list, since we're complete and no future // consumer will ever have to wait.
mWaitingConsumers.Compact();
// If we have no chunks, then there's nothing to compact. if (mChunks.Length() < 1) { return NS_OK;
}
// If we have one chunk, then we can compact if it has excess capacity. if (mChunks.Length() == 1 && mChunks[0].Length() == mChunks[0].Capacity()) { return NS_OK;
}
// If the last chunk has the maximum capacity, then we know the total size // will be quite large and not worth consolidating. We can likely/cheapily // trim the last chunk if it is too big however.
size_t capacity = mChunks.LastElement().Capacity(); if (capacity == MAX_CHUNK_CAPACITY) {
size_t lastLength = mChunks.LastElement().Length(); if (lastLength != capacity) {
mChunks.LastElement().SetCapacity(lastLength);
} return NS_OK;
}
// We can compact our buffer. Determine the total length.
size_t length = 0; for (uint32_t i = 0; i < mChunks.Length(); ++i) {
length += mChunks[i].Length();
}
// If our total length is zero (which means ExpectLength() got called, but no // data ever actually got written) then just empty our chunk list. if (MOZ_UNLIKELY(length == 0)) {
mChunks.Clear(); return NS_OK;
}
Chunk& mergeChunk = mChunks[0]; if (MOZ_UNLIKELY(!mergeChunk.SetCapacity(length))) {
NS_WARNING("Failed to reallocate chunk for SourceBuffer compacting - OOM?"); return NS_OK;
}
// Copy our old chunks into the newly reallocated first chunk. for (uint32_t i = 1; i < mChunks.Length(); ++i) {
size_t offset = mergeChunk.Length();
MOZ_ASSERT(offset < mergeChunk.Capacity());
MOZ_ASSERT(offset + mChunks[i].Length() <= mergeChunk.Capacity());
// Round up to the next multiple of MIN_CHUNK_CAPACITY (which should be the // size of a page).
size_t roundedCapacity =
(aCapacity + MIN_CHUNK_CAPACITY - 1) & ~(MIN_CHUNK_CAPACITY - 1);
MOZ_ASSERT(roundedCapacity >= aCapacity, "Bad math?");
MOZ_ASSERT(roundedCapacity - aCapacity < MIN_CHUNK_CAPACITY, "Bad math?");
if (MOZ_UNLIKELY(mStatus)) {
MOZ_ASSERT_UNREACHABLE("ExpectLength after SourceBuffer is complete"); return NS_OK;
}
if (MOZ_UNLIKELY(mChunks.Length() > 0)) {
MOZ_ASSERT_UNREACHABLE("Duplicate or post-Append call to ExpectLength"); return NS_OK;
}
if (MOZ_UNLIKELY(!SurfaceCache::CanHold(aExpectedLength))) {
NS_WARNING("SourceBuffer refused to store too large buffer"); return HandleError(NS_ERROR_INVALID_ARG);
}
if (MOZ_UNLIKELY(mStatus)) { // This SourceBuffer is already complete; ignore further data. return NS_ERROR_FAILURE;
}
if (MOZ_UNLIKELY(mChunks.Length() == 0)) { if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(CreateChunk(aLength))))) { return HandleError(NS_ERROR_OUT_OF_MEMORY);
}
}
// Copy out the current chunk's information so we can release the lock. // Note that this wouldn't be safe if multiple producers were allowed!
Chunk& currentChunk = mChunks.LastElement();
currentChunkCapacity = currentChunk.Capacity();
currentChunkLength = currentChunk.Length();
currentChunkData = currentChunk.Data();
// Partition this data between the current chunk and the next chunk. // (Because we always allocate a chunk big enough to fit everything passed // to Append, we'll never need more than those two chunks to store // everything.)
currentChunkRemaining = currentChunkCapacity - currentChunkLength;
forCurrentChunk = min(aLength, currentChunkRemaining);
forNextChunk = aLength - forCurrentChunk;
// If we'll need another chunk, determine what its capacity should be while // we still hold the lock.
nextChunkCapacity =
forNextChunk > 0 ? FibonacciCapacityWithMinimum(forNextChunk) : 0;
for (uint32_t i = 0; i < mChunks.Length(); ++i) {
totalCapacity += mChunks[i].Capacity();
}
}
// Write everything we can fit into the current chunk.
MOZ_ASSERT(currentChunkLength + forCurrentChunk <= currentChunkCapacity);
memcpy(currentChunkData + currentChunkLength, aData, forCurrentChunk);
// If there's something left, create a new chunk and write it there.
Maybe<Chunk> nextChunk; if (forNextChunk > 0) {
MOZ_ASSERT(nextChunkCapacity >= forNextChunk, "Next chunk too small?");
nextChunk = CreateChunk(nextChunkCapacity, totalCapacity); if (MOZ_LIKELY(nextChunk && !nextChunk->AllocationFailed())) {
memcpy(nextChunk->Data(), aData + forCurrentChunk, forNextChunk);
nextChunk->AddLength(forNextChunk);
}
}
// Update shared data structures.
{
MutexAutoLock lock(mMutex);
// Update the length of the current chunk.
Chunk& currentChunk = mChunks.LastElement();
MOZ_ASSERT(currentChunk.Data() == currentChunkData, "Multiple producers?");
MOZ_ASSERT(currentChunk.Length() == currentChunkLength, "Multiple producers?");
currentChunk.AddLength(forCurrentChunk);
// If we created a new chunk, add it to the series. if (forNextChunk > 0) { if (MOZ_UNLIKELY(!nextChunk)) { return HandleError(NS_ERROR_OUT_OF_MEMORY);
}
if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(std::move(nextChunk))))) { return HandleError(NS_ERROR_OUT_OF_MEMORY);
}
}
// Resume any waiting readers now that there's new data.
ResumeWaitingConsumers();
}
// Copy the source data. Unless we hit OOM, we squelch the return value here, // because returning an error means that ReadSegments stops reading data, and // we want to ensure that we read everything we get. If we hit OOM then we // return a failed status to the caller.
nsresult rv = sourceBuffer->Append(aFromRawSegment, aCount); if (rv == NS_ERROR_OUT_OF_MEMORY) { return rv;
}
// Report that we wrote everything we got.
*aWriteCount = aCount;
if (bytesRead == 0) { // The loading of the image has been canceled. return NS_ERROR_FAILURE;
}
if (bytesRead != aCount) { // Only some of the given data was read. We may have failed in // SourceBuffer::Append but ReadSegments swallowed the error. Otherwise the // stream itself failed to yield the data.
MutexAutoLock lock(mMutex); if (mStatus) {
MOZ_ASSERT(NS_FAILED(*mStatus)); return *mStatus;
}
MOZ_ASSERT_UNREACHABLE("AppendToSourceBuffer should consume everything");
}
// When an error occurs internally (e.g. due to an OOM), we save the status. // This will indirectly trigger a failure higher up and that will call // SourceBuffer::Complete. Since it doesn't necessarily know we are already // complete, it is safe to ignore. if (mStatus && (MOZ_UNLIKELY(NS_SUCCEEDED(*mStatus) ||
aStatus != NS_IMAGELIB_ERROR_FAILURE))) {
MOZ_ASSERT_UNREACHABLE("Called Complete more than once"); return;
}
if (MOZ_UNLIKELY(NS_SUCCEEDED(aStatus) && IsEmpty())) { // It's illegal to succeed without writing anything.
aStatus = NS_ERROR_FAILURE;
}
mStatus = Some(aStatus);
// Resume any waiting consumers now that we're complete.
ResumeWaitingConsumers();
// If we still have active consumers, just return. if (mConsumerCount > 0) { return;
}
// Attempt to compact our buffer down to a single chunk.
Compact();
}
// Include the bytes the iterator is currently pointing to in the limit, so // that the current chunk doesn't have to be a special case.
size_t bytes = aBytes + iteratorOffset + iteratorLength;
// Count the length over all of our chunks, starting with the one that the // iterator is currently pointing to. (This is O(N), but N is expected to be // ~1, so it doesn't seem worth caching the length separately.)
size_t lengthSoFar = 0; for (uint32_t i = iteratorChunk; i < mChunks.Length(); ++i) {
lengthSoFar += mChunks[i].Length(); if (lengthSoFar > bytes) { returnfalse;
}
}
MOZ_ASSERT(aIterator.HasMore(), "Advancing a completed iterator and " "AdvanceOrScheduleResume didn't catch it");
if (MOZ_UNLIKELY(mStatus && NS_FAILED(*mStatus))) { // This SourceBuffer is complete due to an error; all reads fail. return aIterator.SetComplete(*mStatus);
}
if (MOZ_UNLIKELY(mChunks.Length() == 0)) { // We haven't gotten an initial chunk yet.
AddWaitingConsumer(aConsumer); return aIterator.SetWaiting(!!aConsumer);
}
if (iteratorEnd < currentChunk.Length()) { // There's more data in the current chunk. return aIterator.SetReady(iteratorChunkIdx, currentChunk.Data(),
iteratorEnd, currentChunk.Length() - iteratorEnd,
aRequestedBytes);
}
if (iteratorEnd == currentChunk.Capacity() &&
!IsLastChunk(iteratorChunkIdx)) { // Advance to the next chunk. const Chunk& nextChunk = mChunks[iteratorChunkIdx + 1]; return aIterator.SetReady(iteratorChunkIdx + 1, nextChunk.Data(), 0,
nextChunk.Length(), aRequestedBytes);
}
if (mStatus) { // There's no more data and this SourceBuffer completed successfully.
MOZ_ASSERT(NS_SUCCEEDED(*mStatus), "Handled failures earlier"); return aIterator.SetComplete(*mStatus);
}
// We're not complete, but there's no more data right now. Arrange to wake up // the consumer when we get more data.
AddWaitingConsumer(aConsumer); return aIterator.SetWaiting(!!aConsumer);
}
nsresult SourceBuffer::HandleError(nsresult aError) {
MOZ_ASSERT(NS_FAILED(aError), "Should have an error here");
MOZ_ASSERT(aError == NS_ERROR_OUT_OF_MEMORY || aError == NS_ERROR_INVALID_ARG, "Unexpected error; may want to notify waiting readers, which " "HandleError currently doesn't do");
mMutex.AssertCurrentThreadOwns();
NS_WARNING("SourceBuffer encountered an unrecoverable error");
// Record the error.
mStatus = Some(aError);
// Drop our references to waiting readers.
mWaitingConsumers.Clear();
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.