1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  *
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "ImageLogging.h"  // Must appear first
8 #include "gfxPlatform.h"
9 #include "mozilla/TelemetryHistogramEnums.h"
10 #include "nsWebPDecoder.h"
11 
12 #include "RasterImage.h"
13 #include "SurfacePipeFactory.h"
14 
15 using namespace mozilla::gfx;
16 
17 namespace mozilla {
18 namespace image {
19 
20 static LazyLogModule sWebPLog("WebPDecoder");
21 
nsWebPDecoder(RasterImage * aImage)22 nsWebPDecoder::nsWebPDecoder(RasterImage* aImage)
23     : Decoder(aImage),
24       mDecoder(nullptr),
25       mBlend(BlendMethod::OVER),
26       mDisposal(DisposalMethod::KEEP),
27       mTimeout(FrameTimeout::Forever()),
28       mFormat(SurfaceFormat::OS_RGBX),
29       mLastRow(0),
30       mCurrentFrame(0),
31       mData(nullptr),
32       mLength(0),
33       mIteratorComplete(false),
34       mNeedDemuxer(true),
35       mGotColorProfile(false) {
36   MOZ_LOG(sWebPLog, LogLevel::Debug,
37           ("[this=%p] nsWebPDecoder::nsWebPDecoder", this));
38 }
39 
~nsWebPDecoder()40 nsWebPDecoder::~nsWebPDecoder() {
41   MOZ_LOG(sWebPLog, LogLevel::Debug,
42           ("[this=%p] nsWebPDecoder::~nsWebPDecoder", this));
43   if (mDecoder) {
44     WebPIDelete(mDecoder);
45     WebPFreeDecBuffer(&mBuffer);
46   }
47 }
48 
ReadData()49 LexerResult nsWebPDecoder::ReadData() {
50   MOZ_ASSERT(mData);
51   MOZ_ASSERT(mLength > 0);
52 
53   WebPDemuxer* demuxer = nullptr;
54   bool complete = mIteratorComplete;
55 
56   if (mNeedDemuxer) {
57     WebPDemuxState state;
58     WebPData fragment;
59     fragment.bytes = mData;
60     fragment.size = mLength;
61 
62     demuxer = WebPDemuxPartial(&fragment, &state);
63     if (state == WEBP_DEMUX_PARSE_ERROR) {
64       MOZ_LOG(
65           sWebPLog, LogLevel::Error,
66           ("[this=%p] nsWebPDecoder::ReadData -- demux parse error\n", this));
67       WebPDemuxDelete(demuxer);
68       return LexerResult(TerminalState::FAILURE);
69     }
70 
71     if (state == WEBP_DEMUX_PARSING_HEADER) {
72       WebPDemuxDelete(demuxer);
73       return LexerResult(Yield::NEED_MORE_DATA);
74     }
75 
76     if (!demuxer) {
77       MOZ_LOG(sWebPLog, LogLevel::Error,
78               ("[this=%p] nsWebPDecoder::ReadData -- no demuxer\n", this));
79       return LexerResult(TerminalState::FAILURE);
80     }
81 
82     complete = complete || state == WEBP_DEMUX_DONE;
83   }
84 
85   LexerResult rv(TerminalState::FAILURE);
86   if (!HasSize()) {
87     rv = ReadHeader(demuxer, complete);
88   } else {
89     rv = ReadPayload(demuxer, complete);
90   }
91 
92   WebPDemuxDelete(demuxer);
93   return rv;
94 }
95 
DoDecode(SourceBufferIterator & aIterator,IResumable * aOnResume)96 LexerResult nsWebPDecoder::DoDecode(SourceBufferIterator& aIterator,
97                                     IResumable* aOnResume) {
98   while (true) {
99     SourceBufferIterator::State state = SourceBufferIterator::COMPLETE;
100     if (!mIteratorComplete) {
101       state = aIterator.AdvanceOrScheduleResume(SIZE_MAX, aOnResume);
102 
103       // We need to remember since we can't advance a complete iterator.
104       mIteratorComplete = state == SourceBufferIterator::COMPLETE;
105     }
106 
107     if (state == SourceBufferIterator::WAITING) {
108       return LexerResult(Yield::NEED_MORE_DATA);
109     }
110 
111     LexerResult rv = UpdateBuffer(aIterator, state);
112     if (rv.is<Yield>() && rv.as<Yield>() == Yield::NEED_MORE_DATA) {
113       // We need to check the iterator to see if more is available before
114       // giving up unless we are already complete.
115       if (mIteratorComplete) {
116         MOZ_LOG(sWebPLog, LogLevel::Error,
117                 ("[this=%p] nsWebPDecoder::DoDecode -- read all data, "
118                  "but needs more\n",
119                  this));
120         return LexerResult(TerminalState::FAILURE);
121       }
122       continue;
123     }
124 
125     return rv;
126   }
127 }
128 
UpdateBuffer(SourceBufferIterator & aIterator,SourceBufferIterator::State aState)129 LexerResult nsWebPDecoder::UpdateBuffer(SourceBufferIterator& aIterator,
130                                         SourceBufferIterator::State aState) {
131   MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
132 
133   switch (aState) {
134     case SourceBufferIterator::READY:
135       if (!aIterator.IsContiguous()) {
136         // We need to buffer. This should be rare, but expensive.
137         break;
138       }
139       if (!mData) {
140         // For as long as we hold onto an iterator, we know the data pointers
141         // to the chunks cannot change underneath us, so save the pointer to
142         // the first block.
143         MOZ_ASSERT(mLength == 0);
144         mData = reinterpret_cast<const uint8_t*>(aIterator.Data());
145       }
146       mLength += aIterator.Length();
147       return ReadData();
148     case SourceBufferIterator::COMPLETE:
149       if (!mData) {
150         // We must have hit an error, such as an OOM, when buffering the
151         // first set of encoded data.
152         MOZ_LOG(
153             sWebPLog, LogLevel::Error,
154             ("[this=%p] nsWebPDecoder::DoDecode -- complete no data\n", this));
155         return LexerResult(TerminalState::FAILURE);
156       }
157       return ReadData();
158     default:
159       MOZ_LOG(sWebPLog, LogLevel::Error,
160               ("[this=%p] nsWebPDecoder::DoDecode -- bad state\n", this));
161       return LexerResult(TerminalState::FAILURE);
162   }
163 
164   // We need to buffer. If we have no data buffered, we need to get everything
165   // from the first chunk of the source buffer before appending the new data.
166   if (mBufferedData.empty()) {
167     MOZ_ASSERT(mData);
168     MOZ_ASSERT(mLength > 0);
169 
170     if (!mBufferedData.append(mData, mLength)) {
171       MOZ_LOG(sWebPLog, LogLevel::Error,
172               ("[this=%p] nsWebPDecoder::DoDecode -- oom, initialize %zu\n",
173                this, mLength));
174       return LexerResult(TerminalState::FAILURE);
175     }
176 
177     MOZ_LOG(sWebPLog, LogLevel::Debug,
178             ("[this=%p] nsWebPDecoder::DoDecode -- buffered %zu bytes\n", this,
179              mLength));
180   }
181 
182   // Append the incremental data from the iterator.
183   if (!mBufferedData.append(aIterator.Data(), aIterator.Length())) {
184     MOZ_LOG(sWebPLog, LogLevel::Error,
185             ("[this=%p] nsWebPDecoder::DoDecode -- oom, append %zu on %zu\n",
186              this, aIterator.Length(), mBufferedData.length()));
187     return LexerResult(TerminalState::FAILURE);
188   }
189 
190   MOZ_LOG(sWebPLog, LogLevel::Debug,
191           ("[this=%p] nsWebPDecoder::DoDecode -- buffered %zu -> %zu bytes\n",
192            this, aIterator.Length(), mBufferedData.length()));
193   mData = mBufferedData.begin();
194   mLength = mBufferedData.length();
195   return ReadData();
196 }
197 
CreateFrame(const OrientedIntRect & aFrameRect)198 nsresult nsWebPDecoder::CreateFrame(const OrientedIntRect& aFrameRect) {
199   MOZ_ASSERT(HasSize());
200   MOZ_ASSERT(!mDecoder);
201 
202   MOZ_LOG(
203       sWebPLog, LogLevel::Debug,
204       ("[this=%p] nsWebPDecoder::CreateFrame -- frame %u, (%d, %d) %d x %d\n",
205        this, mCurrentFrame, aFrameRect.x, aFrameRect.y, aFrameRect.width,
206        aFrameRect.height));
207 
208   if (aFrameRect.width <= 0 || aFrameRect.height <= 0) {
209     MOZ_LOG(sWebPLog, LogLevel::Error,
210             ("[this=%p] nsWebPDecoder::CreateFrame -- bad frame rect\n", this));
211     return NS_ERROR_FAILURE;
212   }
213 
214   // If this is our first frame in an animation and it doesn't cover the
215   // full frame, then we are transparent even if there is no alpha
216   if (mCurrentFrame == 0 && !aFrameRect.IsEqualEdges(FullFrame())) {
217     MOZ_ASSERT(HasAnimation());
218     mFormat = SurfaceFormat::OS_RGBA;
219     PostHasTransparency();
220   }
221 
222   WebPInitDecBuffer(&mBuffer);
223 
224   switch (SurfaceFormat::OS_RGBA) {
225     case SurfaceFormat::B8G8R8A8:
226       mBuffer.colorspace = MODE_BGRA;
227       break;
228     case SurfaceFormat::A8R8G8B8:
229       mBuffer.colorspace = MODE_ARGB;
230       break;
231     case SurfaceFormat::R8G8B8A8:
232       mBuffer.colorspace = MODE_RGBA;
233       break;
234     default:
235       MOZ_ASSERT_UNREACHABLE("Unknown OS_RGBA");
236       return NS_ERROR_FAILURE;
237   }
238 
239   mDecoder = WebPINewDecoder(&mBuffer);
240   if (!mDecoder) {
241     MOZ_LOG(sWebPLog, LogLevel::Error,
242             ("[this=%p] nsWebPDecoder::CreateFrame -- create decoder error\n",
243              this));
244     return NS_ERROR_FAILURE;
245   }
246 
247   // WebP doesn't guarantee that the alpha generated matches the hint in the
248   // header, so we always need to claim the input is BGRA. If the output is
249   // BGRX, swizzling will mask off the alpha channel.
250 #if MOZ_BIG_ENDIAN()
251   mBuffer.colorspace = MODE_ARGB;
252   SurfaceFormat inFormat = mFormat;
253 #else
254   SurfaceFormat inFormat = SurfaceFormat::OS_RGBA;
255 #endif
256 
257   SurfacePipeFlags pipeFlags = SurfacePipeFlags();
258   if (mFormat == SurfaceFormat::OS_RGBA &&
259       !(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA)) {
260     pipeFlags |= SurfacePipeFlags::PREMULTIPLY_ALPHA;
261   }
262 
263   Maybe<AnimationParams> animParams;
264   if (!IsFirstFrameDecode()) {
265     animParams.emplace(aFrameRect.ToUnknownRect(), mTimeout, mCurrentFrame,
266                        mBlend, mDisposal);
267   }
268 
269   Maybe<SurfacePipe> pipe = SurfacePipeFactory::CreateSurfacePipe(
270       this, Size(), OutputSize(), aFrameRect, inFormat, mFormat, animParams,
271       mTransform, pipeFlags);
272   if (!pipe) {
273     MOZ_LOG(sWebPLog, LogLevel::Error,
274             ("[this=%p] nsWebPDecoder::CreateFrame -- no pipe\n", this));
275     return NS_ERROR_FAILURE;
276   }
277 
278   mFrameRect = aFrameRect;
279   mPipe = std::move(*pipe);
280   return NS_OK;
281 }
282 
EndFrame()283 void nsWebPDecoder::EndFrame() {
284   MOZ_ASSERT(HasSize());
285   MOZ_ASSERT(mDecoder);
286 
287   auto opacity = mFormat == SurfaceFormat::OS_RGBA ? Opacity::SOME_TRANSPARENCY
288                                                    : Opacity::FULLY_OPAQUE;
289 
290   MOZ_LOG(sWebPLog, LogLevel::Debug,
291           ("[this=%p] nsWebPDecoder::EndFrame -- frame %u, opacity %d, "
292            "disposal %d, timeout %d, blend %d\n",
293            this, mCurrentFrame, (int)opacity, (int)mDisposal,
294            mTimeout.AsEncodedValueDeprecated(), (int)mBlend));
295 
296   PostFrameStop(opacity);
297   WebPIDelete(mDecoder);
298   WebPFreeDecBuffer(&mBuffer);
299   mDecoder = nullptr;
300   mLastRow = 0;
301   ++mCurrentFrame;
302 }
303 
ApplyColorProfile(const char * aProfile,size_t aLength)304 void nsWebPDecoder::ApplyColorProfile(const char* aProfile, size_t aLength) {
305   MOZ_ASSERT(!mGotColorProfile);
306   mGotColorProfile = true;
307 
308   if (mCMSMode == CMSMode::Off || !GetCMSOutputProfile() ||
309       (mCMSMode == CMSMode::TaggedOnly && !aProfile)) {
310     return;
311   }
312 
313   if (!aProfile) {
314     MOZ_LOG(sWebPLog, LogLevel::Debug,
315             ("[this=%p] nsWebPDecoder::ApplyColorProfile -- not tagged, use "
316              "sRGB transform\n",
317              this));
318     mTransform = GetCMSsRGBTransform(SurfaceFormat::OS_RGBA);
319     return;
320   }
321 
322   mInProfile = qcms_profile_from_memory(aProfile, aLength);
323   if (!mInProfile) {
324     MOZ_LOG(
325         sWebPLog, LogLevel::Error,
326         ("[this=%p] nsWebPDecoder::ApplyColorProfile -- bad color profile\n",
327          this));
328     return;
329   }
330 
331   uint32_t profileSpace = qcms_profile_get_color_space(mInProfile);
332   if (profileSpace != icSigRgbData) {
333     // WebP doesn't produce grayscale data, this must be corrupt.
334     MOZ_LOG(sWebPLog, LogLevel::Error,
335             ("[this=%p] nsWebPDecoder::ApplyColorProfile -- ignoring non-rgb "
336              "color profile\n",
337              this));
338     return;
339   }
340 
341   // Calculate rendering intent.
342   int intent = gfxPlatform::GetRenderingIntent();
343   if (intent == -1) {
344     intent = qcms_profile_get_rendering_intent(mInProfile);
345   }
346 
347   // Create the color management transform.
348   qcms_data_type type = gfxPlatform::GetCMSOSRGBAType();
349   mTransform = qcms_transform_create(mInProfile, type, GetCMSOutputProfile(),
350                                      type, (qcms_intent)intent);
351   MOZ_LOG(sWebPLog, LogLevel::Debug,
352           ("[this=%p] nsWebPDecoder::ApplyColorProfile -- use tagged "
353            "transform\n",
354            this));
355 }
356 
ReadHeader(WebPDemuxer * aDemuxer,bool aIsComplete)357 LexerResult nsWebPDecoder::ReadHeader(WebPDemuxer* aDemuxer, bool aIsComplete) {
358   MOZ_ASSERT(aDemuxer);
359 
360   MOZ_LOG(
361       sWebPLog, LogLevel::Debug,
362       ("[this=%p] nsWebPDecoder::ReadHeader -- %zu bytes\n", this, mLength));
363 
364   uint32_t flags = WebPDemuxGetI(aDemuxer, WEBP_FF_FORMAT_FLAGS);
365 
366   if (!IsMetadataDecode() && !mGotColorProfile) {
367     if (flags & WebPFeatureFlags::ICCP_FLAG) {
368       WebPChunkIterator iter;
369       if (!WebPDemuxGetChunk(aDemuxer, "ICCP", 1, &iter)) {
370         return aIsComplete ? LexerResult(TerminalState::FAILURE)
371                            : LexerResult(Yield::NEED_MORE_DATA);
372       }
373 
374       ApplyColorProfile(reinterpret_cast<const char*>(iter.chunk.bytes),
375                         iter.chunk.size);
376       WebPDemuxReleaseChunkIterator(&iter);
377     } else {
378       ApplyColorProfile(nullptr, 0);
379     }
380   }
381 
382   if (flags & WebPFeatureFlags::ANIMATION_FLAG) {
383     // A metadata decode expects to get the correct first frame timeout which
384     // sadly is not provided by the normal WebP header parsing.
385     WebPIterator iter;
386     if (!WebPDemuxGetFrame(aDemuxer, 1, &iter)) {
387       return aIsComplete ? LexerResult(TerminalState::FAILURE)
388                          : LexerResult(Yield::NEED_MORE_DATA);
389     }
390 
391     PostIsAnimated(FrameTimeout::FromRawMilliseconds(iter.duration));
392     WebPDemuxReleaseIterator(&iter);
393   } else {
394     // Single frames don't need a demuxer to be created.
395     mNeedDemuxer = false;
396   }
397 
398   uint32_t width = WebPDemuxGetI(aDemuxer, WEBP_FF_CANVAS_WIDTH);
399   uint32_t height = WebPDemuxGetI(aDemuxer, WEBP_FF_CANVAS_HEIGHT);
400   if (width > INT32_MAX || height > INT32_MAX) {
401     return LexerResult(TerminalState::FAILURE);
402   }
403 
404   PostSize(width, height);
405 
406   bool alpha = flags & WebPFeatureFlags::ALPHA_FLAG;
407   if (alpha) {
408     mFormat = SurfaceFormat::OS_RGBA;
409     PostHasTransparency();
410   }
411 
412   MOZ_LOG(sWebPLog, LogLevel::Debug,
413           ("[this=%p] nsWebPDecoder::ReadHeader -- %u x %u, alpha %d, "
414            "animation %d, metadata decode %d, first frame decode %d\n",
415            this, width, height, alpha, HasAnimation(), IsMetadataDecode(),
416            IsFirstFrameDecode()));
417 
418   if (IsMetadataDecode()) {
419     return LexerResult(TerminalState::SUCCESS);
420   }
421 
422   return ReadPayload(aDemuxer, aIsComplete);
423 }
424 
ReadPayload(WebPDemuxer * aDemuxer,bool aIsComplete)425 LexerResult nsWebPDecoder::ReadPayload(WebPDemuxer* aDemuxer,
426                                        bool aIsComplete) {
427   if (!HasAnimation()) {
428     auto rv = ReadSingle(mData, mLength, FullFrame());
429     if (rv.is<TerminalState>() &&
430         rv.as<TerminalState>() == TerminalState::SUCCESS) {
431       PostDecodeDone();
432     }
433     return rv;
434   }
435   return ReadMultiple(aDemuxer, aIsComplete);
436 }
437 
ReadSingle(const uint8_t * aData,size_t aLength,const OrientedIntRect & aFrameRect)438 LexerResult nsWebPDecoder::ReadSingle(const uint8_t* aData, size_t aLength,
439                                       const OrientedIntRect& aFrameRect) {
440   MOZ_ASSERT(!IsMetadataDecode());
441   MOZ_ASSERT(aData);
442   MOZ_ASSERT(aLength > 0);
443 
444   MOZ_LOG(
445       sWebPLog, LogLevel::Debug,
446       ("[this=%p] nsWebPDecoder::ReadSingle -- %zu bytes\n", this, aLength));
447 
448   if (!mDecoder && NS_FAILED(CreateFrame(aFrameRect))) {
449     return LexerResult(TerminalState::FAILURE);
450   }
451 
452   bool complete;
453   do {
454     VP8StatusCode status = WebPIUpdate(mDecoder, aData, aLength);
455     switch (status) {
456       case VP8_STATUS_OK:
457         complete = true;
458         break;
459       case VP8_STATUS_SUSPENDED:
460         complete = false;
461         break;
462       default:
463         MOZ_LOG(sWebPLog, LogLevel::Error,
464                 ("[this=%p] nsWebPDecoder::ReadSingle -- append error %d\n",
465                  this, status));
466         return LexerResult(TerminalState::FAILURE);
467     }
468 
469     int lastRow = -1;
470     int width = 0;
471     int height = 0;
472     int stride = 0;
473     uint8_t* rowStart =
474         WebPIDecGetRGB(mDecoder, &lastRow, &width, &height, &stride);
475 
476     MOZ_LOG(
477         sWebPLog, LogLevel::Debug,
478         ("[this=%p] nsWebPDecoder::ReadSingle -- complete %d, read %d rows, "
479          "has %d rows available\n",
480          this, complete, mLastRow, lastRow));
481 
482     if (!rowStart || lastRow == -1 || lastRow == mLastRow) {
483       return LexerResult(Yield::NEED_MORE_DATA);
484     }
485 
486     if (width != mFrameRect.width || height != mFrameRect.height ||
487         stride < mFrameRect.width * 4 || lastRow > mFrameRect.height) {
488       MOZ_LOG(sWebPLog, LogLevel::Error,
489               ("[this=%p] nsWebPDecoder::ReadSingle -- bad (w,h,s) = (%d, %d, "
490                "%d)\n",
491                this, width, height, stride));
492       return LexerResult(TerminalState::FAILURE);
493     }
494 
495     for (int row = mLastRow; row < lastRow; row++) {
496       uint32_t* src = reinterpret_cast<uint32_t*>(rowStart + row * stride);
497       WriteState result = mPipe.WriteBuffer(src);
498 
499       Maybe<SurfaceInvalidRect> invalidRect = mPipe.TakeInvalidRect();
500       if (invalidRect) {
501         PostInvalidation(invalidRect->mInputSpaceRect,
502                          Some(invalidRect->mOutputSpaceRect));
503       }
504 
505       if (result == WriteState::FAILURE) {
506         MOZ_LOG(sWebPLog, LogLevel::Error,
507                 ("[this=%p] nsWebPDecoder::ReadSingle -- write pixels error\n",
508                  this));
509         return LexerResult(TerminalState::FAILURE);
510       }
511 
512       if (result == WriteState::FINISHED) {
513         MOZ_ASSERT(row == lastRow - 1, "There was more data to read?");
514         complete = true;
515         break;
516       }
517     }
518 
519     mLastRow = lastRow;
520   } while (!complete);
521 
522   if (!complete) {
523     return LexerResult(Yield::NEED_MORE_DATA);
524   }
525 
526   EndFrame();
527   return LexerResult(TerminalState::SUCCESS);
528 }
529 
ReadMultiple(WebPDemuxer * aDemuxer,bool aIsComplete)530 LexerResult nsWebPDecoder::ReadMultiple(WebPDemuxer* aDemuxer,
531                                         bool aIsComplete) {
532   MOZ_ASSERT(!IsMetadataDecode());
533   MOZ_ASSERT(aDemuxer);
534 
535   MOZ_LOG(sWebPLog, LogLevel::Debug,
536           ("[this=%p] nsWebPDecoder::ReadMultiple\n", this));
537 
538   bool complete = aIsComplete;
539   WebPIterator iter;
540   auto rv = LexerResult(Yield::NEED_MORE_DATA);
541   if (WebPDemuxGetFrame(aDemuxer, mCurrentFrame + 1, &iter)) {
542     switch (iter.blend_method) {
543       case WEBP_MUX_BLEND:
544         mBlend = BlendMethod::OVER;
545         break;
546       case WEBP_MUX_NO_BLEND:
547         mBlend = BlendMethod::SOURCE;
548         break;
549       default:
550         MOZ_ASSERT_UNREACHABLE("Unhandled blend method");
551         break;
552     }
553 
554     switch (iter.dispose_method) {
555       case WEBP_MUX_DISPOSE_NONE:
556         mDisposal = DisposalMethod::KEEP;
557         break;
558       case WEBP_MUX_DISPOSE_BACKGROUND:
559         mDisposal = DisposalMethod::CLEAR;
560         break;
561       default:
562         MOZ_ASSERT_UNREACHABLE("Unhandled dispose method");
563         break;
564     }
565 
566     mFormat = iter.has_alpha || mCurrentFrame > 0 ? SurfaceFormat::OS_RGBA
567                                                   : SurfaceFormat::OS_RGBX;
568     mTimeout = FrameTimeout::FromRawMilliseconds(iter.duration);
569     OrientedIntRect frameRect(iter.x_offset, iter.y_offset, iter.width,
570                               iter.height);
571 
572     rv = ReadSingle(iter.fragment.bytes, iter.fragment.size, frameRect);
573     complete = complete && !WebPDemuxNextFrame(&iter);
574     WebPDemuxReleaseIterator(&iter);
575   }
576 
577   if (rv.is<TerminalState>() &&
578       rv.as<TerminalState>() == TerminalState::SUCCESS) {
579     // If we extracted one frame, and it is not the last, we need to yield to
580     // the lexer to allow the upper layers to acknowledge the frame.
581     if (!complete && !IsFirstFrameDecode()) {
582       rv = LexerResult(Yield::OUTPUT_AVAILABLE);
583     } else {
584       uint32_t loopCount = WebPDemuxGetI(aDemuxer, WEBP_FF_LOOP_COUNT);
585 
586       MOZ_LOG(sWebPLog, LogLevel::Debug,
587               ("[this=%p] nsWebPDecoder::ReadMultiple -- loop count %u\n", this,
588                loopCount));
589       PostDecodeDone(loopCount - 1);
590     }
591   }
592 
593   return rv;
594 }
595 
SpeedHistogram() const596 Maybe<Telemetry::HistogramID> nsWebPDecoder::SpeedHistogram() const {
597   return Some(Telemetry::IMAGE_DECODE_SPEED_WEBP);
598 }
599 
600 }  // namespace image
601 }  // namespace mozilla
602