tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsWebPDecoder.cpp (20020B)


      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 
     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 
     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 
     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 
     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 
    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 
    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  if (!WebPInitDecBuffer(&mBuffer)) {
    223    MOZ_LOG(
    224        sWebPLog, LogLevel::Error,
    225        ("[this=%p] nsWebPDecoder::CreateFrame -- WebPInitDecBuffer failed\n",
    226         this));
    227    return NS_ERROR_FAILURE;
    228  }
    229 
    230  switch (SurfaceFormat::OS_RGBA) {
    231    case SurfaceFormat::B8G8R8A8:
    232      mBuffer.colorspace = MODE_BGRA;
    233      break;
    234    case SurfaceFormat::A8R8G8B8:
    235      mBuffer.colorspace = MODE_ARGB;
    236      break;
    237    case SurfaceFormat::R8G8B8A8:
    238      mBuffer.colorspace = MODE_RGBA;
    239      break;
    240    default:
    241      MOZ_ASSERT_UNREACHABLE("Unknown OS_RGBA");
    242      return NS_ERROR_FAILURE;
    243  }
    244 
    245  mDecoder = WebPINewDecoder(&mBuffer);
    246  if (!mDecoder) {
    247    MOZ_LOG(sWebPLog, LogLevel::Error,
    248            ("[this=%p] nsWebPDecoder::CreateFrame -- create decoder error\n",
    249             this));
    250    return NS_ERROR_FAILURE;
    251  }
    252 
    253  // WebP doesn't guarantee that the alpha generated matches the hint in the
    254  // header, so we always need to claim the input is BGRA. If the output is
    255  // BGRX, swizzling will mask off the alpha channel.
    256  SurfaceFormat inFormat = SurfaceFormat::OS_RGBA;
    257 
    258  SurfacePipeFlags pipeFlags = SurfacePipeFlags();
    259  if (mFormat == SurfaceFormat::OS_RGBA &&
    260      !(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA)) {
    261    pipeFlags |= SurfacePipeFlags::PREMULTIPLY_ALPHA;
    262  }
    263 
    264  Maybe<AnimationParams> animParams;
    265  if (!IsFirstFrameDecode()) {
    266    animParams.emplace(aFrameRect.ToUnknownRect(), mTimeout, mCurrentFrame,
    267                       mBlend, mDisposal);
    268  }
    269 
    270  Maybe<SurfacePipe> pipe = SurfacePipeFactory::CreateSurfacePipe(
    271      this, Size(), OutputSize(), aFrameRect, inFormat, mFormat, animParams,
    272      mTransform, pipeFlags);
    273  if (!pipe) {
    274    MOZ_LOG(sWebPLog, LogLevel::Error,
    275            ("[this=%p] nsWebPDecoder::CreateFrame -- no pipe\n", this));
    276    return NS_ERROR_FAILURE;
    277  }
    278 
    279  mFrameRect = aFrameRect;
    280  mPipe = std::move(*pipe);
    281  return NS_OK;
    282 }
    283 
    284 void nsWebPDecoder::EndFrame() {
    285  MOZ_ASSERT(HasSize());
    286  MOZ_ASSERT(mDecoder);
    287 
    288  auto opacity = mFormat == SurfaceFormat::OS_RGBA ? Opacity::SOME_TRANSPARENCY
    289                                                   : Opacity::FULLY_OPAQUE;
    290 
    291  MOZ_LOG(sWebPLog, LogLevel::Debug,
    292          ("[this=%p] nsWebPDecoder::EndFrame -- frame %u, opacity %d, "
    293           "disposal %d, timeout %d, blend %d\n",
    294           this, mCurrentFrame, (int)opacity, (int)mDisposal,
    295           mTimeout.AsEncodedValueDeprecated(), (int)mBlend));
    296 
    297  PostFrameStop(opacity);
    298  WebPIDelete(mDecoder);
    299  WebPFreeDecBuffer(&mBuffer);
    300  mDecoder = nullptr;
    301  mLastRow = 0;
    302  ++mCurrentFrame;
    303 }
    304 
    305 void nsWebPDecoder::ApplyColorProfile(const char* aProfile, size_t aLength) {
    306  MOZ_ASSERT(!mGotColorProfile);
    307  mGotColorProfile = true;
    308 
    309  if (mCMSMode == CMSMode::Off || !GetCMSOutputProfile() ||
    310      (mCMSMode == CMSMode::TaggedOnly && !aProfile)) {
    311    return;
    312  }
    313 
    314  if (!aProfile) {
    315    MOZ_LOG(sWebPLog, LogLevel::Debug,
    316            ("[this=%p] nsWebPDecoder::ApplyColorProfile -- not tagged, use "
    317             "sRGB transform\n",
    318             this));
    319    mTransform = GetCMSsRGBTransform(SurfaceFormat::OS_RGBA);
    320    return;
    321  }
    322 
    323  mInProfile = qcms_profile_from_memory(aProfile, aLength);
    324  if (!mInProfile) {
    325    MOZ_LOG(
    326        sWebPLog, LogLevel::Error,
    327        ("[this=%p] nsWebPDecoder::ApplyColorProfile -- bad color profile\n",
    328         this));
    329    return;
    330  }
    331 
    332  uint32_t profileSpace = qcms_profile_get_color_space(mInProfile);
    333  if (profileSpace != icSigRgbData) {
    334    // WebP doesn't produce grayscale data, this must be corrupt.
    335    MOZ_LOG(sWebPLog, LogLevel::Error,
    336            ("[this=%p] nsWebPDecoder::ApplyColorProfile -- ignoring non-rgb "
    337             "color profile\n",
    338             this));
    339    return;
    340  }
    341 
    342  // Calculate rendering intent.
    343  int intent = gfxPlatform::GetRenderingIntent();
    344  if (intent == -1) {
    345    intent = qcms_profile_get_rendering_intent(mInProfile);
    346  }
    347 
    348  // Create the color management transform.
    349  qcms_data_type type = gfxPlatform::GetCMSOSRGBAType();
    350  mTransform = qcms_transform_create(mInProfile, type, GetCMSOutputProfile(),
    351                                     type, (qcms_intent)intent);
    352  MOZ_LOG(sWebPLog, LogLevel::Debug,
    353          ("[this=%p] nsWebPDecoder::ApplyColorProfile -- use tagged "
    354           "transform\n",
    355           this));
    356 }
    357 
    358 LexerResult nsWebPDecoder::ReadHeader(WebPDemuxer* aDemuxer, bool aIsComplete) {
    359  MOZ_ASSERT(aDemuxer);
    360 
    361  MOZ_LOG(
    362      sWebPLog, LogLevel::Debug,
    363      ("[this=%p] nsWebPDecoder::ReadHeader -- %zu bytes\n", this, mLength));
    364 
    365  uint32_t flags = WebPDemuxGetI(aDemuxer, WEBP_FF_FORMAT_FLAGS);
    366 
    367  if (!IsMetadataDecode() && !mGotColorProfile) {
    368    if (flags & WebPFeatureFlags::ICCP_FLAG) {
    369      WebPChunkIterator iter;
    370      if (WebPDemuxGetChunk(aDemuxer, "ICCP", 1, &iter)) {
    371        ApplyColorProfile(reinterpret_cast<const char*>(iter.chunk.bytes),
    372                          iter.chunk.size);
    373        WebPDemuxReleaseChunkIterator(&iter);
    374 
    375      } else {
    376        if (!aIsComplete) {
    377          return LexerResult(Yield::NEED_MORE_DATA);
    378        }
    379 
    380        MOZ_LOG(sWebPLog, LogLevel::Warning,
    381                ("[this=%p] nsWebPDecoder::ReadHeader header specified ICCP "
    382                 "but no ICCP chunk found, ignoring\n",
    383                 this));
    384 
    385        ApplyColorProfile(nullptr, 0);
    386      }
    387    } else {
    388      ApplyColorProfile(nullptr, 0);
    389    }
    390  }
    391 
    392  if (flags & WebPFeatureFlags::ANIMATION_FLAG) {
    393    // The demuxer only knows how many frames it will have once it has the
    394    // complete buffer.
    395    if (WantsFrameCount() && !aIsComplete) {
    396      return LexerResult(Yield::NEED_MORE_DATA);
    397    }
    398 
    399    // A metadata decode expects to get the correct first frame timeout which
    400    // sadly is not provided by the normal WebP header parsing.
    401    WebPIterator iter;
    402    if (!WebPDemuxGetFrame(aDemuxer, 1, &iter)) {
    403      return aIsComplete ? LexerResult(TerminalState::FAILURE)
    404                         : LexerResult(Yield::NEED_MORE_DATA);
    405    }
    406 
    407    PostIsAnimated(FrameTimeout::FromRawMilliseconds(iter.duration));
    408    WebPDemuxReleaseIterator(&iter);
    409 
    410    uint32_t loopCount = WebPDemuxGetI(aDemuxer, WEBP_FF_LOOP_COUNT);
    411    if (loopCount > INT32_MAX) {
    412      loopCount = INT32_MAX;
    413    }
    414 
    415    MOZ_LOG(sWebPLog, LogLevel::Debug,
    416            ("[this=%p] nsWebPDecoder::ReadHeader -- loop count %u\n", this,
    417             loopCount));
    418    PostLoopCount(static_cast<int32_t>(loopCount) - 1);
    419  } else {
    420    // Single frames don't need a demuxer to be created.
    421    mNeedDemuxer = false;
    422  }
    423 
    424  uint32_t width = WebPDemuxGetI(aDemuxer, WEBP_FF_CANVAS_WIDTH);
    425  uint32_t height = WebPDemuxGetI(aDemuxer, WEBP_FF_CANVAS_HEIGHT);
    426  if (width > INT32_MAX || height > INT32_MAX) {
    427    return LexerResult(TerminalState::FAILURE);
    428  }
    429 
    430  PostSize(width, height);
    431 
    432  if (WantsFrameCount()) {
    433    uint32_t frameCount = WebPDemuxGetI(aDemuxer, WEBP_FF_FRAME_COUNT);
    434    PostFrameCount(frameCount);
    435  }
    436 
    437  bool alpha = flags & WebPFeatureFlags::ALPHA_FLAG;
    438  if (alpha) {
    439    mFormat = SurfaceFormat::OS_RGBA;
    440    PostHasTransparency();
    441  }
    442 
    443  MOZ_LOG(sWebPLog, LogLevel::Debug,
    444          ("[this=%p] nsWebPDecoder::ReadHeader -- %u x %u, alpha %d, "
    445           "animation %d, metadata decode %d, first frame decode %d\n",
    446           this, width, height, alpha, HasAnimation(), IsMetadataDecode(),
    447           IsFirstFrameDecode()));
    448 
    449  if (IsMetadataDecode()) {
    450    return LexerResult(TerminalState::SUCCESS);
    451  }
    452 
    453  return ReadPayload(aDemuxer, aIsComplete);
    454 }
    455 
    456 LexerResult nsWebPDecoder::ReadPayload(WebPDemuxer* aDemuxer,
    457                                       bool aIsComplete) {
    458  if (!HasAnimation()) {
    459    auto rv = ReadSingle(mData, mLength, FullFrame());
    460    if (rv.is<TerminalState>() &&
    461        rv.as<TerminalState>() == TerminalState::SUCCESS) {
    462      PostDecodeDone();
    463    }
    464    return rv;
    465  }
    466  return ReadMultiple(aDemuxer, aIsComplete);
    467 }
    468 
    469 LexerResult nsWebPDecoder::ReadSingle(const uint8_t* aData, size_t aLength,
    470                                      const OrientedIntRect& aFrameRect) {
    471  MOZ_ASSERT(!IsMetadataDecode());
    472  MOZ_ASSERT(aData);
    473  MOZ_ASSERT(aLength > 0);
    474 
    475  MOZ_LOG(
    476      sWebPLog, LogLevel::Debug,
    477      ("[this=%p] nsWebPDecoder::ReadSingle -- %zu bytes\n", this, aLength));
    478 
    479  if (!mDecoder && NS_FAILED(CreateFrame(aFrameRect))) {
    480    return LexerResult(TerminalState::FAILURE);
    481  }
    482 
    483  bool complete;
    484  do {
    485    VP8StatusCode status = WebPIUpdate(mDecoder, aData, aLength);
    486    switch (status) {
    487      case VP8_STATUS_OK:
    488        complete = true;
    489        break;
    490      case VP8_STATUS_SUSPENDED:
    491        complete = false;
    492        break;
    493      default:
    494        MOZ_LOG(sWebPLog, LogLevel::Error,
    495                ("[this=%p] nsWebPDecoder::ReadSingle -- append error %d\n",
    496                 this, status));
    497        return LexerResult(TerminalState::FAILURE);
    498    }
    499 
    500    int lastRow = -1;
    501    int width = 0;
    502    int height = 0;
    503    int stride = 0;
    504    uint8_t* rowStart =
    505        WebPIDecGetRGB(mDecoder, &lastRow, &width, &height, &stride);
    506 
    507    MOZ_LOG(
    508        sWebPLog, LogLevel::Debug,
    509        ("[this=%p] nsWebPDecoder::ReadSingle -- complete %d, read %d rows, "
    510         "has %d rows available\n",
    511         this, complete, mLastRow, lastRow));
    512 
    513    if (!rowStart || lastRow == -1 || lastRow == mLastRow) {
    514      return LexerResult(Yield::NEED_MORE_DATA);
    515    }
    516 
    517    if (width != mFrameRect.width || height != mFrameRect.height ||
    518        stride < mFrameRect.width * 4 || lastRow > mFrameRect.height) {
    519      MOZ_LOG(sWebPLog, LogLevel::Error,
    520              ("[this=%p] nsWebPDecoder::ReadSingle -- bad (w,h,s) = (%d, %d, "
    521               "%d)\n",
    522               this, width, height, stride));
    523      return LexerResult(TerminalState::FAILURE);
    524    }
    525 
    526    for (int row = mLastRow; row < lastRow; row++) {
    527      uint32_t* src = reinterpret_cast<uint32_t*>(rowStart + row * stride);
    528      WriteState result = mPipe.WriteBuffer(src);
    529 
    530      Maybe<SurfaceInvalidRect> invalidRect = mPipe.TakeInvalidRect();
    531      if (invalidRect) {
    532        PostInvalidation(invalidRect->mInputSpaceRect,
    533                         Some(invalidRect->mOutputSpaceRect));
    534      }
    535 
    536      if (result == WriteState::FAILURE) {
    537        MOZ_LOG(sWebPLog, LogLevel::Error,
    538                ("[this=%p] nsWebPDecoder::ReadSingle -- write pixels error\n",
    539                 this));
    540        return LexerResult(TerminalState::FAILURE);
    541      }
    542 
    543      if (result == WriteState::FINISHED) {
    544        MOZ_ASSERT(row == lastRow - 1, "There was more data to read?");
    545        complete = true;
    546        break;
    547      }
    548    }
    549 
    550    mLastRow = lastRow;
    551  } while (!complete);
    552 
    553  if (!complete) {
    554    return LexerResult(Yield::NEED_MORE_DATA);
    555  }
    556 
    557  EndFrame();
    558  return LexerResult(TerminalState::SUCCESS);
    559 }
    560 
    561 LexerResult nsWebPDecoder::ReadMultiple(WebPDemuxer* aDemuxer,
    562                                        bool aIsComplete) {
    563  MOZ_ASSERT(!IsMetadataDecode());
    564  MOZ_ASSERT(aDemuxer);
    565 
    566  MOZ_LOG(sWebPLog, LogLevel::Debug,
    567          ("[this=%p] nsWebPDecoder::ReadMultiple\n", this));
    568 
    569  bool complete = aIsComplete;
    570  WebPIterator iter;
    571  auto rv = LexerResult(Yield::NEED_MORE_DATA);
    572  if (WebPDemuxGetFrame(aDemuxer, mCurrentFrame + 1, &iter)) {
    573    switch (iter.blend_method) {
    574      case WEBP_MUX_BLEND:
    575        mBlend = BlendMethod::OVER;
    576        break;
    577      case WEBP_MUX_NO_BLEND:
    578        mBlend = BlendMethod::SOURCE;
    579        break;
    580      default:
    581        MOZ_ASSERT_UNREACHABLE("Unhandled blend method");
    582        break;
    583    }
    584 
    585    switch (iter.dispose_method) {
    586      case WEBP_MUX_DISPOSE_NONE:
    587        mDisposal = DisposalMethod::KEEP;
    588        break;
    589      case WEBP_MUX_DISPOSE_BACKGROUND:
    590        mDisposal = DisposalMethod::CLEAR;
    591        break;
    592      default:
    593        MOZ_ASSERT_UNREACHABLE("Unhandled dispose method");
    594        break;
    595    }
    596 
    597    mFormat = iter.has_alpha || mCurrentFrame > 0 ? SurfaceFormat::OS_RGBA
    598                                                  : SurfaceFormat::OS_RGBX;
    599    mTimeout = FrameTimeout::FromRawMilliseconds(iter.duration);
    600    OrientedIntRect frameRect(iter.x_offset, iter.y_offset, iter.width,
    601                              iter.height);
    602 
    603    rv = ReadSingle(iter.fragment.bytes, iter.fragment.size, frameRect);
    604    complete = complete && !WebPDemuxNextFrame(&iter);
    605    WebPDemuxReleaseIterator(&iter);
    606  }
    607 
    608  if (rv.is<TerminalState>() &&
    609      rv.as<TerminalState>() == TerminalState::SUCCESS) {
    610    // If we extracted one frame, and it is not the last, we need to yield to
    611    // the lexer to allow the upper layers to acknowledge the frame.
    612    if (!complete && !IsFirstFrameDecode()) {
    613      rv = LexerResult(Yield::OUTPUT_AVAILABLE);
    614    } else {
    615      PostDecodeDone();
    616    }
    617  }
    618 
    619  return rv;
    620 }
    621 
    622 Maybe<glean::impl::MemoryDistributionMetric> nsWebPDecoder::SpeedMetric()
    623    const {
    624  return Some(glean::image_decode::speed_webp);
    625 }
    626 
    627 }  // namespace image
    628 }  // namespace mozilla