Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.

Commit 414919e

Browse files
committed
Bug 1503935 - Fix some WebP decoder implementation bugs. r=tnikkel
First we did not handle the SourceBufferIterator::WAITING state which can happen when we get woken up but there is no data to read from the SourceBufferIterator. StreamingLexer handled this properly by yielding with NEED_MORE_DATA, and properly scheduling the decoder to resume. This patch does the same in the WebP decoder. Second nsWebPDecoder::GetType was not implemented. This meant it would return DecoderType::UNKNOWN, and would fail to recreate the decoder if we are discarding frames and need to restart from the beginning. In addition to implementing that method, this patch also corrects an assert in DecoderFactory::CloneAnimationDecoder which failed to check for WebP as a supported animated decoder. This patch also modestly improves the logging output and library method checks. Differential Revision: https://phabricator.services.mozilla.com/D10624
1 parent e471e32 commit 414919e

10 files changed

Lines changed: 167 additions & 26 deletions

File tree

image/DecoderFactory.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,8 @@ DecoderFactory::CloneAnimationDecoder(Decoder* aDecoder)
246246
// get scheduled yet, or it has only decoded the first frame and has yet to
247247
// rediscover it is animated).
248248
DecoderType type = aDecoder->GetType();
249-
MOZ_ASSERT(type == DecoderType::GIF || type == DecoderType::PNG,
249+
MOZ_ASSERT(type == DecoderType::GIF || type == DecoderType::PNG ||
250+
type == DecoderType::WEBP,
250251
"Calling CloneAnimationDecoder for non-animating DecoderType");
251252

252253
RefPtr<Decoder> decoder = GetDecoder(type, nullptr, /* aIsRedecode = */ true);

image/IDecodingTask.cpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,10 @@ MetadataDecodingTask::Run()
185185
// AnonymousDecodingTask implementation.
186186
///////////////////////////////////////////////////////////////////////////////
187187

188-
AnonymousDecodingTask::AnonymousDecodingTask(NotNull<Decoder*> aDecoder)
188+
AnonymousDecodingTask::AnonymousDecodingTask(NotNull<Decoder*> aDecoder,
189+
bool aResumable)
189190
: mDecoder(aDecoder)
191+
, mResumable(aResumable)
190192
{ }
191193

192194
void
@@ -211,5 +213,20 @@ AnonymousDecodingTask::Run()
211213
}
212214
}
213215

216+
void
217+
AnonymousDecodingTask::Resume()
218+
{
219+
// Anonymous decoders normally get all their data at once. We have tests
220+
// where they don't; typically in these situations, the test re-runs them
221+
// manually. However some tests want to verify Resume works, so they will
222+
// explicitly request this behaviour.
223+
if (mResumable) {
224+
RefPtr<AnonymousDecodingTask> self(this);
225+
NS_DispatchToMainThread(NS_NewRunnableFunction(
226+
"image::AnonymousDecodingTask::Resume",
227+
[self]() -> void { self->Run(); }));
228+
}
229+
}
230+
214231
} // namespace image
215232
} // namespace mozilla

image/IDecodingTask.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,22 +109,21 @@ class AnonymousDecodingTask final : public IDecodingTask
109109
public:
110110
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AnonymousDecodingTask, override)
111111

112-
explicit AnonymousDecodingTask(NotNull<Decoder*> aDecoder);
112+
explicit AnonymousDecodingTask(NotNull<Decoder*> aDecoder,
113+
bool aResumable);
113114

114115
void Run() override;
115116

116117
bool ShouldPreferSyncRun() const override { return true; }
117118
TaskPriority Priority() const override { return TaskPriority::eLow; }
118119

119-
// Anonymous decoders normally get all their data at once. We have tests where
120-
// they don't; in these situations, the test re-runs them manually. So no
121-
// matter what, we don't want to resume by posting a task to the DecodePool.
122-
void Resume() override { }
120+
void Resume() override;
123121

124122
private:
125123
virtual ~AnonymousDecodingTask() { }
126124

127125
NotNull<RefPtr<Decoder>> mDecoder;
126+
bool mResumable;
128127
};
129128

130129
} // namespace image

image/ImageOps.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ ImageOps::DecodeMetadata(ImageBuffer* aBuffer,
179179
}
180180

181181
// Run the decoder synchronously.
182-
RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
182+
RefPtr<IDecodingTask> task =
183+
new AnonymousDecodingTask(WrapNotNull(decoder), /* aResumable */ false);
183184
task->Run();
184185
if (!decoder->GetDecodeDone() || decoder->HasError()) {
185186
return NS_ERROR_FAILURE;
@@ -233,7 +234,8 @@ ImageOps::DecodeToSurface(ImageBuffer* aBuffer,
233234
}
234235

235236
// Run the decoder synchronously.
236-
RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
237+
RefPtr<IDecodingTask> task =
238+
new AnonymousDecodingTask(WrapNotNull(decoder), /* aResumable */ false);
237239
task->Run();
238240
if (!decoder->GetDecodeDone() || decoder->HasError()) {
239241
return nullptr;

image/decoders/nsWebPDecoder.cpp

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ nsWebPDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume)
110110

111111
SourceBufferIterator::State state = SourceBufferIterator::COMPLETE;
112112
if (!mIteratorComplete) {
113-
state = aIterator.Advance(SIZE_MAX);
113+
state = aIterator.AdvanceOrScheduleResume(SIZE_MAX, aOnResume);
114114

115115
// We need to remember since we can't advance a complete iterator.
116116
mIteratorComplete = state == SourceBufferIterator::COMPLETE;
@@ -133,6 +133,8 @@ nsWebPDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume)
133133
return ReadData();
134134
case SourceBufferIterator::COMPLETE:
135135
return ReadData();
136+
case SourceBufferIterator::WAITING:
137+
return LexerResult(Yield::NEED_MORE_DATA);
136138
default:
137139
MOZ_LOG(sWebPLog, LogLevel::Error,
138140
("[this=%p] nsWebPDecoder::DoDecode -- bad state\n", this));
@@ -180,8 +182,16 @@ nsWebPDecoder::CreateFrame(const nsIntRect& aFrameRect)
180182
MOZ_ASSERT(!mDecoder);
181183

182184
MOZ_LOG(sWebPLog, LogLevel::Debug,
183-
("[this=%p] nsWebPDecoder::CreateFrame -- frame %u, %d x %d\n",
184-
this, mCurrentFrame, aFrameRect.width, aFrameRect.height));
185+
("[this=%p] nsWebPDecoder::CreateFrame -- frame %u, (%d, %d) %d x %d\n",
186+
this, mCurrentFrame, aFrameRect.x, aFrameRect.y,
187+
aFrameRect.width, aFrameRect.height));
188+
189+
if (aFrameRect.width <= 0 || aFrameRect.height <= 0) {
190+
MOZ_LOG(sWebPLog, LogLevel::Error,
191+
("[this=%p] nsWebPDecoder::CreateFrame -- bad frame rect\n",
192+
this));
193+
return NS_ERROR_FAILURE;
194+
}
185195

186196
// If this is our first frame in an animation and it doesn't cover the
187197
// full frame, then we are transparent even if there is no alpha
@@ -220,6 +230,7 @@ nsWebPDecoder::CreateFrame(const nsIntRect& aFrameRect)
220230
return NS_ERROR_FAILURE;
221231
}
222232

233+
mFrameRect = aFrameRect;
223234
mPipe = std::move(*pipe);
224235
return NS_OK;
225236
}
@@ -419,7 +430,9 @@ nsWebPDecoder::ReadSingle(const uint8_t* aData, size_t aLength, const IntRect& a
419430
return LexerResult(Yield::NEED_MORE_DATA);
420431
}
421432

422-
if (width <= 0 || height <= 0 || stride <= 0) {
433+
if (width != mFrameRect.width || height != mFrameRect.height ||
434+
stride < mFrameRect.width * 4 ||
435+
lastRow > mFrameRect.height) {
423436
MOZ_LOG(sWebPLog, LogLevel::Error,
424437
("[this=%p] nsWebPDecoder::ReadSingle -- bad (w,h,s) = (%d, %d, %d)\n",
425438
this, width, height, stride));

image/decoders/nsWebPDecoder.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ class nsWebPDecoder final : public Decoder
2121
public:
2222
virtual ~nsWebPDecoder();
2323

24+
DecoderType GetType() const override { return DecoderType::WEBP; }
25+
2426
protected:
2527
LexerResult DoDecode(SourceBufferIterator& aIterator,
2628
IResumable* aOnResume) override;
@@ -77,6 +79,9 @@ class nsWebPDecoder final : public Decoder
7779
/// Surface format for the current frame.
7880
gfx::SurfaceFormat mFormat;
7981

82+
/// Frame rect for the current frame.
83+
IntRect mFrameRect;
84+
8085
/// The last row of decoded pixels written to mPipe.
8186
int mLastRow;
8287

image/test/gtest/Common.cpp

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,7 @@ AutoInitializeImageLib::AutoInitializeImageLib()
5555
// Depending on initialization order, it is possible that our pref changes
5656
// have not taken effect yet because there are pending gfx-related events on
5757
// the main thread.
58-
nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
59-
EXPECT_TRUE(mainThread != nullptr);
60-
61-
bool processed;
62-
do {
63-
processed = false;
64-
nsresult rv = mainThread->ProcessNextEvent(false, &processed);
65-
EXPECT_TRUE(NS_SUCCEEDED(rv));
66-
} while (processed);
58+
SpinPendingEvents();
6759
}
6860

6961
///////////////////////////////////////////////////////////////////////////////
@@ -102,6 +94,20 @@ AutoInitializeImageLib::AutoInitializeImageLib()
10294
return rv; \
10395
}
10496

97+
void
98+
SpinPendingEvents()
99+
{
100+
nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
101+
EXPECT_TRUE(mainThread != nullptr);
102+
103+
bool processed;
104+
do {
105+
processed = false;
106+
nsresult rv = mainThread->ProcessNextEvent(false, &processed);
107+
EXPECT_TRUE(NS_SUCCEEDED(rv));
108+
} while (processed);
109+
}
110+
105111
already_AddRefed<nsIInputStream>
106112
LoadFile(const char* aRelativePath)
107113
{

image/test/gtest/Common.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ class AutoInitializeImageLib
133133
AutoInitializeImageLib();
134134
};
135135

136+
/// Spins on the main thread to process any pending events.
137+
void SpinPendingEvents();
138+
136139
/// Loads a file from the current directory. @return an nsIInputStream for it.
137140
already_AddRefed<nsIInputStream> LoadFile(const char* aRelativePath);
138141

image/test/gtest/TestDecoders.cpp

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ using namespace mozilla::image;
3434
static already_AddRefed<SourceSurface>
3535
CheckDecoderState(const ImageTestCase& aTestCase, Decoder* aDecoder)
3636
{
37+
// Decoder should match what we asked for in the MIME type.
38+
EXPECT_NE(aDecoder->GetType(), DecoderType::UNKNOWN);
39+
EXPECT_EQ(aDecoder->GetType(),
40+
DecoderFactory::GetDecoderType(aTestCase.mMimeType));
41+
3742
EXPECT_TRUE(aDecoder->GetDecodeDone());
3843
EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_HAS_ERROR),
3944
aDecoder->HasError());
@@ -119,7 +124,8 @@ void WithSingleChunkDecode(const ImageTestCase& aTestCase,
119124
DecoderFlags::FIRST_FRAME_ONLY,
120125
DefaultSurfaceFlags());
121126
ASSERT_TRUE(decoder != nullptr);
122-
RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
127+
RefPtr<IDecodingTask> task =
128+
new AnonymousDecodingTask(WrapNotNull(decoder), /* aResumable */ false);
123129

124130
// Run the full decoder synchronously.
125131
task->Run();
@@ -136,6 +142,58 @@ CheckDecoderSingleChunk(const ImageTestCase& aTestCase)
136142
});
137143
}
138144

145+
template <typename Func>
146+
void WithDelayedChunkDecode(const ImageTestCase& aTestCase,
147+
const Maybe<IntSize>& aOutputSize,
148+
Func aResultChecker)
149+
{
150+
nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
151+
ASSERT_TRUE(inputStream != nullptr);
152+
153+
// Figure out how much data we have.
154+
uint64_t length;
155+
nsresult rv = inputStream->Available(&length);
156+
ASSERT_TRUE(NS_SUCCEEDED(rv));
157+
158+
// Prepare an empty SourceBuffer.
159+
auto sourceBuffer = MakeNotNull<RefPtr<SourceBuffer>>();
160+
161+
// Create a decoder.
162+
DecoderType decoderType =
163+
DecoderFactory::GetDecoderType(aTestCase.mMimeType);
164+
RefPtr<Decoder> decoder =
165+
DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, aOutputSize,
166+
DecoderFlags::FIRST_FRAME_ONLY,
167+
DefaultSurfaceFlags());
168+
ASSERT_TRUE(decoder != nullptr);
169+
RefPtr<IDecodingTask> task =
170+
new AnonymousDecodingTask(WrapNotNull(decoder), /* aResumable */ true);
171+
172+
// Run the full decoder synchronously. It should now be waiting on
173+
// the iterator to yield some data since we haven't written anything yet.
174+
task->Run();
175+
176+
// Writing all of the data should wake up the decoder to complete.
177+
sourceBuffer->ExpectLength(length);
178+
rv = sourceBuffer->AppendFromInputStream(inputStream, length);
179+
ASSERT_TRUE(NS_SUCCEEDED(rv));
180+
sourceBuffer->Complete(NS_OK);
181+
182+
// It would have gotten posted to the main thread to avoid mutex contention.
183+
SpinPendingEvents();
184+
185+
// Call the lambda to verify the expected results.
186+
aResultChecker(decoder);
187+
}
188+
189+
static void
190+
CheckDecoderDelayedChunk(const ImageTestCase& aTestCase)
191+
{
192+
WithDelayedChunkDecode(aTestCase, Nothing(), [&](Decoder* aDecoder) {
193+
CheckDecoderResults(aTestCase, aDecoder);
194+
});
195+
}
196+
139197
static void
140198
CheckDecoderMultiChunk(const ImageTestCase& aTestCase)
141199
{
@@ -157,7 +215,8 @@ CheckDecoderMultiChunk(const ImageTestCase& aTestCase)
157215
DecoderFlags::FIRST_FRAME_ONLY,
158216
DefaultSurfaceFlags());
159217
ASSERT_TRUE(decoder != nullptr);
160-
RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
218+
RefPtr<IDecodingTask> task =
219+
new AnonymousDecodingTask(WrapNotNull(decoder), /* aResumable */ false);
161220

162221
for (uint64_t read = 0; read < length ; ++read) {
163222
uint64_t available = 0;
@@ -581,6 +640,11 @@ TEST_F(ImageDecoders, PNGSingleChunk)
581640
CheckDecoderSingleChunk(GreenPNGTestCase());
582641
}
583642

643+
TEST_F(ImageDecoders, PNGDelayedChunk)
644+
{
645+
CheckDecoderDelayedChunk(GreenPNGTestCase());
646+
}
647+
584648
TEST_F(ImageDecoders, PNGMultiChunk)
585649
{
586650
CheckDecoderMultiChunk(GreenPNGTestCase());
@@ -596,6 +660,11 @@ TEST_F(ImageDecoders, GIFSingleChunk)
596660
CheckDecoderSingleChunk(GreenGIFTestCase());
597661
}
598662

663+
TEST_F(ImageDecoders, GIFDelayedChunk)
664+
{
665+
CheckDecoderDelayedChunk(GreenGIFTestCase());
666+
}
667+
599668
TEST_F(ImageDecoders, GIFMultiChunk)
600669
{
601670
CheckDecoderMultiChunk(GreenGIFTestCase());
@@ -611,6 +680,11 @@ TEST_F(ImageDecoders, JPGSingleChunk)
611680
CheckDecoderSingleChunk(GreenJPGTestCase());
612681
}
613682

683+
TEST_F(ImageDecoders, JPGDelayedChunk)
684+
{
685+
CheckDecoderDelayedChunk(GreenJPGTestCase());
686+
}
687+
614688
TEST_F(ImageDecoders, JPGMultiChunk)
615689
{
616690
CheckDecoderMultiChunk(GreenJPGTestCase());
@@ -626,6 +700,11 @@ TEST_F(ImageDecoders, BMPSingleChunk)
626700
CheckDecoderSingleChunk(GreenBMPTestCase());
627701
}
628702

703+
TEST_F(ImageDecoders, BMPDelayedChunk)
704+
{
705+
CheckDecoderDelayedChunk(GreenBMPTestCase());
706+
}
707+
629708
TEST_F(ImageDecoders, BMPMultiChunk)
630709
{
631710
CheckDecoderMultiChunk(GreenBMPTestCase());
@@ -641,6 +720,11 @@ TEST_F(ImageDecoders, ICOSingleChunk)
641720
CheckDecoderSingleChunk(GreenICOTestCase());
642721
}
643722

723+
TEST_F(ImageDecoders, ICODelayedChunk)
724+
{
725+
CheckDecoderDelayedChunk(GreenICOTestCase());
726+
}
727+
644728
TEST_F(ImageDecoders, ICOMultiChunk)
645729
{
646730
CheckDecoderMultiChunk(GreenICOTestCase());
@@ -661,6 +745,11 @@ TEST_F(ImageDecoders, IconSingleChunk)
661745
CheckDecoderSingleChunk(GreenIconTestCase());
662746
}
663747

748+
TEST_F(ImageDecoders, IconDelayedChunk)
749+
{
750+
CheckDecoderDelayedChunk(GreenIconTestCase());
751+
}
752+
664753
TEST_F(ImageDecoders, IconMultiChunk)
665754
{
666755
CheckDecoderMultiChunk(GreenIconTestCase());
@@ -676,6 +765,11 @@ TEST_F(ImageDecoders, WebPSingleChunk)
676765
CheckDecoderSingleChunk(GreenWebPTestCase());
677766
}
678767

768+
TEST_F(ImageDecoders, WebPDelayedChunk)
769+
{
770+
CheckDecoderDelayedChunk(GreenWebPTestCase());
771+
}
772+
679773
TEST_F(ImageDecoders, WebPMultiChunk)
680774
{
681775
CheckDecoderMultiChunk(GreenWebPTestCase());

0 commit comments

Comments
 (0)