1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "SourceBuffer.h"
11 #include "mozilla/Likely.h"
12 #include "nsIInputStream.h"
13 #include "MainThreadUtils.h"
14 #include "SurfaceCache.h"
22 //////////////////////////////////////////////////////////////////////////////
23 // SourceBufferIterator implementation.
24 //////////////////////////////////////////////////////////////////////////////
26 SourceBufferIterator::~SourceBufferIterator() {
28 mOwner
->OnIteratorRelease();
32 SourceBufferIterator
& SourceBufferIterator::operator=(
33 SourceBufferIterator
&& aOther
) {
35 mOwner
->OnIteratorRelease();
38 mOwner
= std::move(aOther
.mOwner
);
39 mState
= aOther
.mState
;
41 mChunkCount
= aOther
.mChunkCount
;
42 mByteCount
= aOther
.mByteCount
;
43 mRemainderToRead
= aOther
.mRemainderToRead
;
48 SourceBufferIterator::State
SourceBufferIterator::AdvanceOrScheduleResume(
49 size_t aRequestedBytes
, IResumable
* aConsumer
) {
52 if (MOZ_UNLIKELY(!HasMore())) {
53 MOZ_ASSERT_UNREACHABLE("Should not advance a completed iterator");
57 // The range of data [mOffset, mOffset + mNextReadLength) has just been read
58 // by the caller (or at least they don't have any interest in it), so consume
60 MOZ_ASSERT(mData
.mIterating
.mNextReadLength
<=
61 mData
.mIterating
.mAvailableLength
);
62 mData
.mIterating
.mOffset
+= mData
.mIterating
.mNextReadLength
;
63 mData
.mIterating
.mAvailableLength
-= mData
.mIterating
.mNextReadLength
;
65 // An iterator can have a limit imposed on it to read only a subset of a
66 // source buffer. If it is present, we need to mimic the same behaviour as
67 // the owning SourceBuffer.
68 if (MOZ_UNLIKELY(mRemainderToRead
!= SIZE_MAX
)) {
69 MOZ_ASSERT(mData
.mIterating
.mNextReadLength
<= mRemainderToRead
);
70 mRemainderToRead
-= mData
.mIterating
.mNextReadLength
;
72 if (MOZ_UNLIKELY(mRemainderToRead
== 0)) {
73 mData
.mIterating
.mNextReadLength
= 0;
78 if (MOZ_UNLIKELY(aRequestedBytes
> mRemainderToRead
)) {
79 aRequestedBytes
= mRemainderToRead
;
83 mData
.mIterating
.mNextReadLength
= 0;
85 if (MOZ_LIKELY(mState
== READY
)) {
86 // If the caller wants zero bytes of data, that's easy enough; we just
87 // configured ourselves for a zero-byte read above! In theory we could do
88 // this even in the START state, but it's not important for performance and
89 // breaking the ability of callers to assert that the pointer returned by
90 // Data() is non-null doesn't seem worth it.
91 if (aRequestedBytes
== 0) {
92 MOZ_ASSERT(mData
.mIterating
.mNextReadLength
== 0);
96 // Try to satisfy the request out of our local buffer. This is potentially
97 // much faster than requesting data from our owning SourceBuffer because we
98 // don't have to take the lock. Note that if we have anything at all in our
99 // local buffer, we use it to satisfy the request; @aRequestedBytes is just
100 // the *maximum* number of bytes we can return.
101 if (mData
.mIterating
.mAvailableLength
> 0) {
102 return AdvanceFromLocalBuffer(aRequestedBytes
);
106 // Our local buffer is empty, so we'll have to request data from our owning
108 return mOwner
->AdvanceIteratorOrScheduleResume(*this, aRequestedBytes
,
112 bool SourceBufferIterator::RemainingBytesIsNoMoreThan(size_t aBytes
) const {
114 return mOwner
->RemainingBytesIsNoMoreThan(*this, aBytes
);
117 //////////////////////////////////////////////////////////////////////////////
118 // SourceBuffer implementation.
119 //////////////////////////////////////////////////////////////////////////////
121 const size_t SourceBuffer::MIN_CHUNK_CAPACITY
;
122 const size_t SourceBuffer::MAX_CHUNK_CAPACITY
;
124 SourceBuffer::SourceBuffer()
125 : mMutex("image::SourceBuffer"), mConsumerCount(0), mCompacted(false) {}
127 SourceBuffer::~SourceBuffer() {
128 MOZ_ASSERT(mConsumerCount
== 0,
129 "SourceBuffer destroyed with active consumers");
132 nsresult
SourceBuffer::AppendChunk(Maybe
<Chunk
>&& aChunk
) {
133 mMutex
.AssertCurrentThreadOwns();
136 if (mChunks
.Length() > 0) {
137 NS_WARNING("Appending an extra chunk for SourceBuffer");
141 if (MOZ_UNLIKELY(!aChunk
)) {
142 return NS_ERROR_OUT_OF_MEMORY
;
145 if (MOZ_UNLIKELY(aChunk
->AllocationFailed())) {
146 return NS_ERROR_OUT_OF_MEMORY
;
149 if (MOZ_UNLIKELY(!mChunks
.AppendElement(std::move(*aChunk
), fallible
))) {
150 return NS_ERROR_OUT_OF_MEMORY
;
156 Maybe
<SourceBuffer::Chunk
> SourceBuffer::CreateChunk(
157 size_t aCapacity
, size_t aExistingCapacity
/* = 0 */,
158 bool aRoundUp
/* = true */) {
159 if (MOZ_UNLIKELY(aCapacity
== 0)) {
160 MOZ_ASSERT_UNREACHABLE("Appending a chunk of zero size?");
164 // Round up if requested.
165 size_t finalCapacity
= aRoundUp
? RoundedUpCapacity(aCapacity
) : aCapacity
;
167 // Use the size of the SurfaceCache as an additional heuristic to avoid
168 // allocating huge buffers. Generally images do not get smaller when decoded,
169 // so if we could store the source data in the SurfaceCache, we assume that
170 // there's no way we'll be able to store the decoded version.
171 if (MOZ_UNLIKELY(!SurfaceCache::CanHold(finalCapacity
+ aExistingCapacity
))) {
173 "SourceBuffer refused to create chunk too large for SurfaceCache");
177 return Some(Chunk(finalCapacity
));
180 nsresult
SourceBuffer::Compact() {
181 mMutex
.AssertCurrentThreadOwns();
183 MOZ_ASSERT(mConsumerCount
== 0, "Should have no consumers here");
184 MOZ_ASSERT(mWaitingConsumers
.Length() == 0, "Shouldn't have waiters");
185 MOZ_ASSERT(mStatus
, "Should be complete here");
187 // If we've tried to compact once, don't attempt again.
194 // Compact our waiting consumers list, since we're complete and no future
195 // consumer will ever have to wait.
196 mWaitingConsumers
.Compact();
198 // If we have no chunks, then there's nothing to compact.
199 if (mChunks
.Length() < 1) {
203 // If we have one chunk, then we can compact if it has excess capacity.
204 if (mChunks
.Length() == 1 && mChunks
[0].Length() == mChunks
[0].Capacity()) {
208 // If the last chunk has the maximum capacity, then we know the total size
209 // will be quite large and not worth consolidating. We can likely/cheapily
210 // trim the last chunk if it is too big however.
211 size_t capacity
= mChunks
.LastElement().Capacity();
212 if (capacity
== MAX_CHUNK_CAPACITY
) {
213 size_t lastLength
= mChunks
.LastElement().Length();
214 if (lastLength
!= capacity
) {
215 mChunks
.LastElement().SetCapacity(lastLength
);
220 // We can compact our buffer. Determine the total length.
222 for (uint32_t i
= 0; i
< mChunks
.Length(); ++i
) {
223 length
+= mChunks
[i
].Length();
226 // If our total length is zero (which means ExpectLength() got called, but no
227 // data ever actually got written) then just empty our chunk list.
228 if (MOZ_UNLIKELY(length
== 0)) {
233 Chunk
& mergeChunk
= mChunks
[0];
234 if (MOZ_UNLIKELY(!mergeChunk
.SetCapacity(length
))) {
235 NS_WARNING("Failed to reallocate chunk for SourceBuffer compacting - OOM?");
239 // Copy our old chunks into the newly reallocated first chunk.
240 for (uint32_t i
= 1; i
< mChunks
.Length(); ++i
) {
241 size_t offset
= mergeChunk
.Length();
242 MOZ_ASSERT(offset
< mergeChunk
.Capacity());
243 MOZ_ASSERT(offset
+ mChunks
[i
].Length() <= mergeChunk
.Capacity());
245 memcpy(mergeChunk
.Data() + offset
, mChunks
[i
].Data(), mChunks
[i
].Length());
246 mergeChunk
.AddLength(mChunks
[i
].Length());
249 MOZ_ASSERT(mergeChunk
.Length() == mergeChunk
.Capacity(),
250 "Compacted chunk has slack space");
252 // Remove the redundant chunks.
253 mChunks
.RemoveLastElements(mChunks
.Length() - 1);
260 size_t SourceBuffer::RoundedUpCapacity(size_t aCapacity
) {
261 // Protect against overflow.
262 if (MOZ_UNLIKELY(SIZE_MAX
- aCapacity
< MIN_CHUNK_CAPACITY
)) {
266 // Round up to the next multiple of MIN_CHUNK_CAPACITY (which should be the
268 size_t roundedCapacity
=
269 (aCapacity
+ MIN_CHUNK_CAPACITY
- 1) & ~(MIN_CHUNK_CAPACITY
- 1);
270 MOZ_ASSERT(roundedCapacity
>= aCapacity
, "Bad math?");
271 MOZ_ASSERT(roundedCapacity
- aCapacity
< MIN_CHUNK_CAPACITY
, "Bad math?");
273 return roundedCapacity
;
276 size_t SourceBuffer::FibonacciCapacityWithMinimum(size_t aMinCapacity
) {
277 mMutex
.AssertCurrentThreadOwns();
279 // We grow the source buffer using a Fibonacci growth rate. It will be capped
280 // at MAX_CHUNK_CAPACITY, unless the available data exceeds that.
282 size_t length
= mChunks
.Length();
284 if (length
== 0 || aMinCapacity
> MAX_CHUNK_CAPACITY
) {
289 return min(max(2 * mChunks
[0].Capacity(), aMinCapacity
),
294 max(mChunks
[length
- 1].Capacity() + mChunks
[length
- 2].Capacity(),
299 void SourceBuffer::AddWaitingConsumer(IResumable
* aConsumer
) {
300 mMutex
.AssertCurrentThreadOwns();
302 MOZ_ASSERT(!mStatus
, "Waiting when we're complete?");
305 mWaitingConsumers
.AppendElement(aConsumer
);
309 void SourceBuffer::ResumeWaitingConsumers() {
310 mMutex
.AssertCurrentThreadOwns();
312 if (mWaitingConsumers
.Length() == 0) {
316 for (uint32_t i
= 0; i
< mWaitingConsumers
.Length(); ++i
) {
317 mWaitingConsumers
[i
]->Resume();
320 mWaitingConsumers
.Clear();
323 nsresult
SourceBuffer::ExpectLength(size_t aExpectedLength
) {
324 MOZ_ASSERT(aExpectedLength
> 0, "Zero expected size?");
326 MutexAutoLock
lock(mMutex
);
328 if (MOZ_UNLIKELY(mStatus
)) {
329 MOZ_ASSERT_UNREACHABLE("ExpectLength after SourceBuffer is complete");
333 if (MOZ_UNLIKELY(mChunks
.Length() > 0)) {
334 MOZ_ASSERT_UNREACHABLE("Duplicate or post-Append call to ExpectLength");
338 if (MOZ_UNLIKELY(!SurfaceCache::CanHold(aExpectedLength
))) {
339 NS_WARNING("SourceBuffer refused to store too large buffer");
340 return HandleError(NS_ERROR_INVALID_ARG
);
343 size_t length
= min(aExpectedLength
, MAX_CHUNK_CAPACITY
);
344 if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(CreateChunk(length
,
345 /* aExistingCapacity */ 0,
346 /* aRoundUp */ false))))) {
347 return HandleError(NS_ERROR_OUT_OF_MEMORY
);
353 nsresult
SourceBuffer::Append(const char* aData
, size_t aLength
) {
354 MOZ_ASSERT(aData
, "Should have a buffer");
355 MOZ_ASSERT(aLength
> 0, "Writing a zero-sized chunk");
357 size_t currentChunkCapacity
= 0;
358 size_t currentChunkLength
= 0;
359 char* currentChunkData
= nullptr;
360 size_t currentChunkRemaining
= 0;
361 size_t forCurrentChunk
= 0;
362 size_t forNextChunk
= 0;
363 size_t nextChunkCapacity
= 0;
364 size_t totalCapacity
= 0;
367 MutexAutoLock
lock(mMutex
);
369 if (MOZ_UNLIKELY(mStatus
)) {
370 // This SourceBuffer is already complete; ignore further data.
371 return NS_ERROR_FAILURE
;
374 if (MOZ_UNLIKELY(mChunks
.Length() == 0)) {
375 if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(CreateChunk(aLength
))))) {
376 return HandleError(NS_ERROR_OUT_OF_MEMORY
);
380 // Copy out the current chunk's information so we can release the lock.
381 // Note that this wouldn't be safe if multiple producers were allowed!
382 Chunk
& currentChunk
= mChunks
.LastElement();
383 currentChunkCapacity
= currentChunk
.Capacity();
384 currentChunkLength
= currentChunk
.Length();
385 currentChunkData
= currentChunk
.Data();
387 // Partition this data between the current chunk and the next chunk.
388 // (Because we always allocate a chunk big enough to fit everything passed
389 // to Append, we'll never need more than those two chunks to store
391 currentChunkRemaining
= currentChunkCapacity
- currentChunkLength
;
392 forCurrentChunk
= min(aLength
, currentChunkRemaining
);
393 forNextChunk
= aLength
- forCurrentChunk
;
395 // If we'll need another chunk, determine what its capacity should be while
396 // we still hold the lock.
398 forNextChunk
> 0 ? FibonacciCapacityWithMinimum(forNextChunk
) : 0;
400 for (uint32_t i
= 0; i
< mChunks
.Length(); ++i
) {
401 totalCapacity
+= mChunks
[i
].Capacity();
405 // Write everything we can fit into the current chunk.
406 MOZ_ASSERT(currentChunkLength
+ forCurrentChunk
<= currentChunkCapacity
);
407 memcpy(currentChunkData
+ currentChunkLength
, aData
, forCurrentChunk
);
409 // If there's something left, create a new chunk and write it there.
410 Maybe
<Chunk
> nextChunk
;
411 if (forNextChunk
> 0) {
412 MOZ_ASSERT(nextChunkCapacity
>= forNextChunk
, "Next chunk too small?");
413 nextChunk
= CreateChunk(nextChunkCapacity
, totalCapacity
);
414 if (MOZ_LIKELY(nextChunk
&& !nextChunk
->AllocationFailed())) {
415 memcpy(nextChunk
->Data(), aData
+ forCurrentChunk
, forNextChunk
);
416 nextChunk
->AddLength(forNextChunk
);
420 // Update shared data structures.
422 MutexAutoLock
lock(mMutex
);
424 // Update the length of the current chunk.
425 Chunk
& currentChunk
= mChunks
.LastElement();
426 MOZ_ASSERT(currentChunk
.Data() == currentChunkData
, "Multiple producers?");
427 MOZ_ASSERT(currentChunk
.Length() == currentChunkLength
,
428 "Multiple producers?");
430 currentChunk
.AddLength(forCurrentChunk
);
432 // If we created a new chunk, add it to the series.
433 if (forNextChunk
> 0) {
434 if (MOZ_UNLIKELY(!nextChunk
)) {
435 return HandleError(NS_ERROR_OUT_OF_MEMORY
);
438 if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(std::move(nextChunk
))))) {
439 return HandleError(NS_ERROR_OUT_OF_MEMORY
);
443 // Resume any waiting readers now that there's new data.
444 ResumeWaitingConsumers();
450 static nsresult
AppendToSourceBuffer(nsIInputStream
*, void* aClosure
,
451 const char* aFromRawSegment
, uint32_t,
452 uint32_t aCount
, uint32_t* aWriteCount
) {
453 SourceBuffer
* sourceBuffer
= static_cast<SourceBuffer
*>(aClosure
);
455 // Copy the source data. Unless we hit OOM, we squelch the return value here,
456 // because returning an error means that ReadSegments stops reading data, and
457 // we want to ensure that we read everything we get. If we hit OOM then we
458 // return a failed status to the caller.
459 nsresult rv
= sourceBuffer
->Append(aFromRawSegment
, aCount
);
460 if (rv
== NS_ERROR_OUT_OF_MEMORY
) {
464 // Report that we wrote everything we got.
465 *aWriteCount
= aCount
;
470 nsresult
SourceBuffer::AppendFromInputStream(nsIInputStream
* aInputStream
,
473 nsresult rv
= aInputStream
->ReadSegments(AppendToSourceBuffer
, this, aCount
,
475 if (NS_WARN_IF(NS_FAILED(rv
))) {
479 if (bytesRead
== 0) {
480 // The loading of the image has been canceled.
481 return NS_ERROR_FAILURE
;
484 if (bytesRead
!= aCount
) {
485 // Only some of the given data was read. We may have failed in
486 // SourceBuffer::Append but ReadSegments swallowed the error. Otherwise the
487 // stream itself failed to yield the data.
488 MutexAutoLock
lock(mMutex
);
490 MOZ_ASSERT(NS_FAILED(*mStatus
));
494 MOZ_ASSERT_UNREACHABLE("AppendToSourceBuffer should consume everything");
500 void SourceBuffer::Complete(nsresult aStatus
) {
501 MutexAutoLock
lock(mMutex
);
503 // When an error occurs internally (e.g. due to an OOM), we save the status.
504 // This will indirectly trigger a failure higher up and that will call
505 // SourceBuffer::Complete. Since it doesn't necessarily know we are already
506 // complete, it is safe to ignore.
507 if (mStatus
&& (MOZ_UNLIKELY(NS_SUCCEEDED(*mStatus
) ||
508 aStatus
!= NS_IMAGELIB_ERROR_FAILURE
))) {
509 MOZ_ASSERT_UNREACHABLE("Called Complete more than once");
513 if (MOZ_UNLIKELY(NS_SUCCEEDED(aStatus
) && IsEmpty())) {
514 // It's illegal to succeed without writing anything.
515 aStatus
= NS_ERROR_FAILURE
;
518 mStatus
= Some(aStatus
);
520 // Resume any waiting consumers now that we're complete.
521 ResumeWaitingConsumers();
523 // If we still have active consumers, just return.
524 if (mConsumerCount
> 0) {
528 // Attempt to compact our buffer down to a single chunk.
532 bool SourceBuffer::IsComplete() {
533 MutexAutoLock
lock(mMutex
);
534 return bool(mStatus
);
537 size_t SourceBuffer::SizeOfIncludingThisWithComputedFallback(
538 MallocSizeOf aMallocSizeOf
) const {
539 MutexAutoLock
lock(mMutex
);
541 size_t n
= aMallocSizeOf(this);
542 n
+= mChunks
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
544 for (uint32_t i
= 0; i
< mChunks
.Length(); ++i
) {
545 size_t chunkSize
= aMallocSizeOf(mChunks
[i
].Data());
547 if (chunkSize
== 0) {
548 // We're on a platform where moz_malloc_size_of always returns 0.
549 chunkSize
= mChunks
[i
].Capacity();
558 SourceBufferIterator
SourceBuffer::Iterator(size_t aReadLength
) {
560 MutexAutoLock
lock(mMutex
);
564 return SourceBufferIterator(this, aReadLength
);
567 void SourceBuffer::OnIteratorRelease() {
568 MutexAutoLock
lock(mMutex
);
570 MOZ_ASSERT(mConsumerCount
> 0, "Consumer count doesn't add up");
573 // If we still have active consumers, or we're not complete yet, then return.
574 if (mConsumerCount
> 0 || !mStatus
) {
578 // Attempt to compact our buffer down to a single chunk.
582 bool SourceBuffer::RemainingBytesIsNoMoreThan(
583 const SourceBufferIterator
& aIterator
, size_t aBytes
) const {
584 MutexAutoLock
lock(mMutex
);
586 // If we're not complete, we always say no.
591 // If the iterator's at the end, the answer is trivial.
592 if (!aIterator
.HasMore()) {
596 uint32_t iteratorChunk
= aIterator
.mData
.mIterating
.mChunk
;
597 size_t iteratorOffset
= aIterator
.mData
.mIterating
.mOffset
;
598 size_t iteratorLength
= aIterator
.mData
.mIterating
.mAvailableLength
;
600 // Include the bytes the iterator is currently pointing to in the limit, so
601 // that the current chunk doesn't have to be a special case.
602 size_t bytes
= aBytes
+ iteratorOffset
+ iteratorLength
;
604 // Count the length over all of our chunks, starting with the one that the
605 // iterator is currently pointing to. (This is O(N), but N is expected to be
606 // ~1, so it doesn't seem worth caching the length separately.)
607 size_t lengthSoFar
= 0;
608 for (uint32_t i
= iteratorChunk
; i
< mChunks
.Length(); ++i
) {
609 lengthSoFar
+= mChunks
[i
].Length();
610 if (lengthSoFar
> bytes
) {
618 SourceBufferIterator::State
SourceBuffer::AdvanceIteratorOrScheduleResume(
619 SourceBufferIterator
& aIterator
, size_t aRequestedBytes
,
620 IResumable
* aConsumer
) {
621 MutexAutoLock
lock(mMutex
);
623 MOZ_ASSERT(aIterator
.HasMore(),
624 "Advancing a completed iterator and "
625 "AdvanceOrScheduleResume didn't catch it");
627 if (MOZ_UNLIKELY(mStatus
&& NS_FAILED(*mStatus
))) {
628 // This SourceBuffer is complete due to an error; all reads fail.
629 return aIterator
.SetComplete(*mStatus
);
632 if (MOZ_UNLIKELY(mChunks
.Length() == 0)) {
633 // We haven't gotten an initial chunk yet.
634 AddWaitingConsumer(aConsumer
);
635 return aIterator
.SetWaiting(!!aConsumer
);
638 uint32_t iteratorChunkIdx
= aIterator
.mData
.mIterating
.mChunk
;
639 MOZ_ASSERT(iteratorChunkIdx
< mChunks
.Length());
641 const Chunk
& currentChunk
= mChunks
[iteratorChunkIdx
];
642 size_t iteratorEnd
= aIterator
.mData
.mIterating
.mOffset
+
643 aIterator
.mData
.mIterating
.mAvailableLength
;
644 MOZ_ASSERT(iteratorEnd
<= currentChunk
.Length());
645 MOZ_ASSERT(iteratorEnd
<= currentChunk
.Capacity());
647 if (iteratorEnd
< currentChunk
.Length()) {
648 // There's more data in the current chunk.
649 return aIterator
.SetReady(iteratorChunkIdx
, currentChunk
.Data(),
650 iteratorEnd
, currentChunk
.Length() - iteratorEnd
,
654 if (iteratorEnd
== currentChunk
.Capacity() &&
655 !IsLastChunk(iteratorChunkIdx
)) {
656 // Advance to the next chunk.
657 const Chunk
& nextChunk
= mChunks
[iteratorChunkIdx
+ 1];
658 return aIterator
.SetReady(iteratorChunkIdx
+ 1, nextChunk
.Data(), 0,
659 nextChunk
.Length(), aRequestedBytes
);
662 MOZ_ASSERT(IsLastChunk(iteratorChunkIdx
), "Should've advanced");
665 // There's no more data and this SourceBuffer completed successfully.
666 MOZ_ASSERT(NS_SUCCEEDED(*mStatus
), "Handled failures earlier");
667 return aIterator
.SetComplete(*mStatus
);
670 // We're not complete, but there's no more data right now. Arrange to wake up
671 // the consumer when we get more data.
672 AddWaitingConsumer(aConsumer
);
673 return aIterator
.SetWaiting(!!aConsumer
);
676 nsresult
SourceBuffer::HandleError(nsresult aError
) {
677 MOZ_ASSERT(NS_FAILED(aError
), "Should have an error here");
678 MOZ_ASSERT(aError
== NS_ERROR_OUT_OF_MEMORY
|| aError
== NS_ERROR_INVALID_ARG
,
679 "Unexpected error; may want to notify waiting readers, which "
680 "HandleError currently doesn't do");
682 mMutex
.AssertCurrentThreadOwns();
684 NS_WARNING("SourceBuffer encountered an unrecoverable error");
687 mStatus
= Some(aError
);
689 // Drop our references to waiting readers.
690 mWaitingConsumers
.Clear();
695 bool SourceBuffer::IsEmpty() {
696 mMutex
.AssertCurrentThreadOwns();
697 return mChunks
.Length() == 0 || mChunks
[0].Length() == 0;
700 bool SourceBuffer::IsLastChunk(uint32_t aChunk
) {
701 mMutex
.AssertCurrentThreadOwns();
702 return aChunk
+ 1 == mChunks
.Length();
706 } // namespace mozilla