DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Implementation

Mercurial (d38398e5144e)

VCS Links

BlockList

Entry

MediaCacheStream

ReadMode

Macros

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */

#ifndef MediaCache_h_
#define MediaCache_h_

#include "nsTArray.h"
#include "nsCOMPtr.h"
#include "nsHashKeys.h"
#include "nsTHashtable.h"
#include "Intervals.h"
#include "mozilla/UniquePtr.h"

class nsIPrincipal;

namespace mozilla {
// defined in MediaResource.h
class ChannelMediaResource;
typedef media::IntervalSet<int64_t> MediaByteRangeSet;
class MediaResource;
class ReentrantMonitorAutoEnter;

/**
 * Media applications want fast, "on demand" random access to media data,
 * for pausing, seeking, etc. But we are primarily interested
 * in transporting media data using HTTP over the Internet, which has
 * high latency to open a connection, requires a new connection for every
 * seek, may not even support seeking on some connections (especially
 * live streams), and uses a push model --- data comes from the server
 * and you don't have much control over the rate. Also, transferring data
 * over the Internet can be slow and/or unpredictable, so we want to read
 * ahead to buffer and cache as much data as possible.
 *
 * The job of the media cache is to resolve this impedance mismatch.
 * The media cache reads data from Necko channels into file-backed storage,
 * and offers a random-access file-like API to the stream data
 * (MediaCacheStream). Along the way it solves several problems:
 * -- The cache intelligently reads ahead to prefetch data that may be
 * needed in the future
 * -- The size of the cache is bounded so that we don't fill up
 * storage with read-ahead data
 * -- Cache replacement is managed globally so that the most valuable
 * data (across all streams) is retained
 * -- The cache can suspend Necko channels temporarily when their data is
 * not wanted (yet)
 * -- The cache translates file-like seek requests to HTTP seeks,
 * including optimizations like not triggering a new seek if it would
 * be faster to just keep reading until we reach the seek point. The
 * "seek to EOF" idiom to determine file size is also handled efficiently
 * (seeking to EOF and then seeking back to the previous offset does not
 * trigger any Necko activity)
 * -- The cache also handles the case where the server does not support
 * seeking
 * -- Necko can only send data to the main thread, but MediaCacheStream
 * can distribute data to any thread
 * -- The cache exposes APIs so clients can detect what data is
 * currently held
 *
 * Note that although HTTP is the most important transport and we only
 * support transport-level seeking via HTTP byte-ranges, the media cache
 * works with any kind of Necko channels and provides random access to
 * cached data even for, e.g., FTP streams.
 *
 * The media cache is not persistent. It does not currently allow
 * data from one load to be used by other loads, either within the same
 * browser session or across browser sessions. The media cache file
 * is marked "delete on close" so it will automatically disappear in the
 * event of a browser crash or shutdown.
 *
 * The media cache is block-based. Streams are divided into blocks of a
 * fixed size (currently 4K) and we cache blocks. A single cache contains
 * blocks for all streams.
 *
 * The cache size is controlled by the media.cache_size preference
 * (which is in KB). The default size is 500MB.
 *
 * The replacement policy predicts a "time of next use" for each block
 * in the cache. When we need to free a block, the block with the latest
 * "time of next use" will be evicted. Blocks are divided into
 * different classes, each class having its own predictor:
 * FREE_BLOCK: these blocks are effectively infinitely far in the future;
 * a free block will always be chosen for replacement before other classes
 * of blocks.
 * METADATA_BLOCK: these are blocks that contain data that has been read
 * by the decoder in "metadata mode", e.g. while the decoder is searching
 * the stream during a seek operation. These blocks are managed with an
 * LRU policy; the "time of next use" is predicted to be as far in the
 * future as the last use was in the past.
 * PLAYED_BLOCK: these are blocks that have not been read in "metadata
 * mode", and contain data behind the current decoder read point. (They
 * may not actually have been read by the decoder, if the decoder seeked
 * forward.) These blocks are managed with an LRU policy except that we add
 * REPLAY_DELAY seconds of penalty to their predicted "time of next use",
 * to reflect the uncertainty about whether replay will actually happen
 * or not.
 * READAHEAD_BLOCK: these are blocks that have not been read in
 * "metadata mode" and that are entirely ahead of the current decoder
 * read point. (They may actually have been read by the decoder in the
 * past if the decoder has since seeked backward.) We predict the
 * time of next use for these blocks by assuming steady playback and
 * dividing the number of bytes between the block and the current decoder
 * read point by the decoder's estimate of its playback rate in bytes
 * per second. This ensures that the blocks farthest ahead are considered
 * least valuable.
 * For efficient prediction of the "latest time of next use", we maintain
 * linked lists of blocks in each class, ordering blocks by time of
 * next use. READAHEAD_BLOCKS have one linked list per stream, since their
 * time of next use depends on stream parameters, but the other lists
 * are global.
 *
 * A block containing a current decoder read point can contain data
 * both behind and ahead of the read point. It will be classified as a
 * PLAYED_BLOCK but we will give it special treatment so it is never
 * evicted --- it actually contains the highest-priority readahead data
 * as well as played data.
 *
 * "Time of next use" estimates are also used for flow control. When
 * reading ahead we can predict the time of next use for the data that
 * will be read. If the predicted time of next use is later then the
 * prediction for all currently cached blocks, and the cache is full, then
 * we should suspend reading from the Necko channel.
 *
 * Unfortunately suspending the Necko channel can't immediately stop the
 * flow of data from the server. First our desire to suspend has to be
 * transmitted to the server (in practice, Necko stops reading from the
 * socket, which causes the kernel to shrink its advertised TCP receive
 * window size to zero). Then the server can stop sending the data, but
 * we will receive data roughly corresponding to the product of the link
 * bandwidth multiplied by the round-trip latency. We deal with this by
 * letting the cache overflow temporarily and then trimming it back by
 * moving overflowing blocks back into the body of the cache, replacing
 * less valuable blocks as they become available. We try to avoid simply
 * discarding overflowing readahead data.
 *
 * All changes to the actual contents of the cache happen on the main
 * thread, since that's where Necko's notifications happen.
 *
 * The media cache maintains at most one Necko channel for each stream.
 * (In the future it might be advantageous to relax this, e.g. so that a
 * seek to near the end of the file can happen without disturbing
 * the loading of data from the beginning of the file.) The Necko channel
 * is managed through ChannelMediaResource; MediaCache does not
 * depend on Necko directly.
 *
 * Every time something changes that might affect whether we want to
 * read from a Necko channel, or whether we want to seek on the Necko
 * channel --- such as data arriving or data being consumed by the
 * decoder --- we asynchronously trigger MediaCache::Update on the main
 * thread. That method implements most cache policy. It evaluates for
 * each stream whether we want to suspend or resume the stream and what
 * offset we should seek to, if any. It is also responsible for trimming
 * back the cache size to its desired limit by moving overflowing blocks
 * into the main part of the cache.
 *
 * Streams can be opened in non-seekable mode. In non-seekable mode,
 * the cache will only call ChannelMediaResource::CacheClientSeek with
 * a 0 offset. The cache tries hard not to discard readahead data
 * for non-seekable streams, since that could trigger a potentially
 * disastrous re-read of the entire stream. It's up to cache clients
 * to try to avoid requesting seeks on such streams.
 *
 * MediaCache has a single internal monitor for all synchronization.
 * This is treated as the lowest level monitor in the media code. So,
 * we must not acquire any MediaDecoder locks or MediaResource locks
 * while holding the MediaCache lock. But it's OK to hold those locks
 * and then get the MediaCache lock.
 *
 * MediaCache associates a principal with each stream. CacheClientSeek
 * can trigger new HTTP requests; due to redirects to other domains,
 * each HTTP load can return data with a different principal. This
 * principal must be passed to NotifyDataReceived, and MediaCache
 * will detect when different principals are associated with data in the
 * same stream, and replace them with a null principal.
 */
class MediaCache;

/**
 * If the cache fails to initialize then Init will fail, so nonstatic
 * methods of this class can assume gMediaCache is non-null.
 *
 * This class can be directly embedded as a value.
 */
class MediaCacheStream {
public:
  // This needs to be a power of two
  static const int64_t BLOCK_SIZE = 32768;

  enum ReadMode {
    MODE_METADATA,
    MODE_PLAYBACK
  };

  // aClient provides the underlying transport that cache will use to read
  // data for this stream.
  explicit MediaCacheStream(ChannelMediaResource* aClient);
  ~MediaCacheStream();

  // Set up this stream with the cache. Can fail on OOM. One
  // of InitAsClone or Init must be called before any other method on
  // this class. Does nothing if already initialized.
  nsresult Init();

  // Set up this stream with the cache, assuming it's for the same data
  // as the aOriginal stream. Can fail on OOM. Exactly one
  // of InitAsClone or Init must be called before any other method on
  // this class. Does nothing if already initialized.
  nsresult InitAsClone(MediaCacheStream* aOriginal);

  // These are called on the main thread.
  // Tell us whether the stream is seekable or not. Non-seekable streams
  // will always pass 0 for aOffset to CacheClientSeek. This should only
  // be called while the stream is at channel offset 0. Seekability can
  // change during the lifetime of the MediaCacheStream --- every time
  // we do an HTTP load the seekability may be different (and sometimes
  // is, in practice, due to the effects of caching proxies).
  void SetTransportSeekable(bool aIsTransportSeekable);
  // This must be called (and return) before the ChannelMediaResource
  // used to create this MediaCacheStream is deleted.
  void Close();
  // This returns true when the stream has been closed
  bool IsClosed() const { return mClosed; }
  // Returns true when this stream is can be shared by a new resource load
  bool IsAvailableForSharing() const
  {
    return !mClosed &&
      (!mDidNotifyDataEnded || NS_SUCCEEDED(mNotifyDataEndedStatus));
  }
  // Get the principal for this stream. Anything accessing the contents of
  // this stream must have a principal that subsumes this principal.
  nsIPrincipal* GetCurrentPrincipal() { return mPrincipal; }
  // Ensure a global media cache update has run with this stream present.
  // This ensures the cache has had a chance to suspend or unsuspend this stream.
  // Called only on main thread. This can change the state of streams, fire
  // notifications, etc.
  void EnsureCacheUpdate();

  // These callbacks are called on the main thread by the client
  // when data has been received via the channel.
  // Tells the cache what the server said the data length is going to be.
  // The actual data length may be greater (we receive more data than
  // specified) or smaller (the stream ends before we reach the given
  // length), because servers can lie. The server's reported data length
  // *and* the actual data length can even vary over time because a
  // misbehaving server may feed us a different stream after each seek
  // operation. So this is really just a hint. The cache may however
  // stop reading (suspend the channel) when it thinks we've read all the
  // data available based on an incorrect reported length. Seeks relative
  // EOF also depend on the reported length if we haven't managed to
  // read the whole stream yet.
  void NotifyDataLength(int64_t aLength);
  // Notifies the cache that a load has begun. We pass the offset
  // because in some cases the offset might not be what the cache
  // requested. In particular we might unexpectedly start providing
  // data at offset 0. This need not be called if the offset is the
  // offset that the cache requested in
  // ChannelMediaResource::CacheClientSeek. This can be called at any
  // time by the client, not just after a CacheClientSeek.
  void NotifyDataStarted(int64_t aOffset);
  // Notifies the cache that data has been received. The stream already
  // knows the offset because data is received in sequence and
  // the starting offset is known via NotifyDataStarted or because
  // the cache requested the offset in
  // ChannelMediaResource::CacheClientSeek, or because it defaulted to 0.
  // We pass in the principal that was used to load this data.
  void NotifyDataReceived(int64_t aSize, const char* aData,
                          nsIPrincipal* aPrincipal);
  // Notifies the cache that the current bytes should be written to disk.
  // Called on the main thread.
  void FlushPartialBlock();
  // Notifies the cache that the channel has closed with the given status.
  void NotifyDataEnded(nsresult aStatus);

  // Notifies the stream that the channel is reopened. The stream should
  // reset variables such as |mDidNotifyDataEnded|.
  void NotifyChannelRecreated();

  // These methods can be called on any thread.
  // Cached blocks associated with this stream will not be evicted
  // while the stream is pinned.
  void Pin();
  void Unpin();
  // See comments above for NotifyDataLength about how the length
  // can vary over time. Returns -1 if no length is known. Returns the
  // reported length if we haven't got any better information. If
  // the stream ended normally we return the length we actually got.
  // If we've successfully read data beyond the originally reported length,
  // we return the end of the data we've read.
  int64_t GetLength();
  // Returns the unique resource ID. Call only on the main thread or while
  // holding the media cache lock.
  int64_t GetResourceID() { return mResourceID; }
  // Returns the end of the bytes starting at the given offset
  // which are in cache.
  int64_t GetCachedDataEnd(int64_t aOffset);
  // Returns the offset of the first byte of cached data at or after aOffset,
  // or -1 if there is no such cached data.
  int64_t GetNextCachedData(int64_t aOffset);
  // Fills aRanges with the ByteRanges representing the data which is currently
  // cached. Locks the media cache while running, to prevent any ranges
  // growing. The stream should be pinned while this runs and while its results
  // are used, to ensure no data is evicted.
  nsresult GetCachedRanges(MediaByteRangeSet& aRanges);

  // Reads from buffered data only. Will fail if not all data to be read is
  // in the cache. Will not mark blocks as read. Can be called from the main
  // thread. It's the caller's responsibility to wrap the call in a pin/unpin,
  // and also to check that the range they want is cached before calling this.
  nsresult ReadFromCache(char* aBuffer,
                         int64_t aOffset,
                         int64_t aCount);

  // IsDataCachedToEndOfStream returns true if all the data from
  // aOffset to the end of the stream (the server-reported end, if the
  // real end is not known) is in cache. If we know nothing about the
  // end of the stream, this returns false.
  bool IsDataCachedToEndOfStream(int64_t aOffset);
  // The mode is initially MODE_PLAYBACK.
  void SetReadMode(ReadMode aMode);
  // This is the client's estimate of the playback rate assuming
  // the media plays continuously. The cache can't guess this itself
  // because it doesn't know when the decoder was paused, buffering, etc.
  // Do not pass zero.
  void SetPlaybackRate(uint32_t aBytesPerSecond);
  // Returns the last set value of SetTransportSeekable.
  bool IsTransportSeekable();

  // Returns true when all streams for this resource are suspended or their
  // channel has ended.
  bool AreAllStreamsForResourceSuspended();

  // These methods must be called on a different thread from the main
  // thread. They should always be called on the same thread for a given
  // stream.
  // This can fail when aWhence is NS_SEEK_END and no stream length
  // is known.
  nsresult Seek(int32_t aWhence, int64_t aOffset);
  int64_t Tell();
  // *aBytes gets the number of bytes that were actually read. This can
  // be less than aCount. If the first byte of data is not in the cache,
  // this will block until the data is available or the stream is
  // closed, otherwise it won't block.
  nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes);
  // Seeks to aOffset in the stream then performs a Read operation. See
  // 'Read' for argument and return details.
  nsresult ReadAt(int64_t aOffset, char* aBuffer,
                  uint32_t aCount, uint32_t* aBytes);

  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;

private:
  friend class MediaCache;

  /**
   * A doubly-linked list of blocks. Add/Remove/Get methods are all
   * constant time. We declare this here so that a stream can contain a
   * BlockList of its read-ahead blocks. Blocks are referred to by index
   * into the MediaCache::mIndex array.
   *
   * Blocks can belong to more than one list at the same time, because
   * the next/prev pointers are not stored in the block.
   */
  class BlockList {
  public:
    BlockList() : mFirstBlock(-1), mCount(0) {}
    ~BlockList() {
      NS_ASSERTION(mFirstBlock == -1 && mCount == 0,
                   "Destroying non-empty block list");
    }
    void AddFirstBlock(int32_t aBlock);
    void AddAfter(int32_t aBlock, int32_t aBefore);
    void RemoveBlock(int32_t aBlock);
    // Returns the first block in the list, or -1 if empty
    int32_t GetFirstBlock() const { return mFirstBlock; }
    // Returns the last block in the list, or -1 if empty
    int32_t GetLastBlock() const;
    // Returns the next block in the list after aBlock or -1 if
    // aBlock is the last block
    int32_t GetNextBlock(int32_t aBlock) const;
    // Returns the previous block in the list before aBlock or -1 if
    // aBlock is the first block
    int32_t GetPrevBlock(int32_t aBlock) const;
    bool IsEmpty() const { return mFirstBlock < 0; }
    int32_t GetCount() const { return mCount; }
    // The contents of aBlockIndex1 and aBlockIndex2 have been swapped
    void NotifyBlockSwapped(int32_t aBlockIndex1, int32_t aBlockIndex2);
#ifdef DEBUG
    // Verify linked-list invariants
    void Verify();
#else
    void Verify() {}
#endif

    size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;

  private:
    struct Entry : public nsUint32HashKey {
      explicit Entry(KeyTypePointer aKey) : nsUint32HashKey(aKey) { }
      Entry(const Entry& toCopy) : nsUint32HashKey(&toCopy.GetKey()),
        mNextBlock(toCopy.mNextBlock), mPrevBlock(toCopy.mPrevBlock) {}

      int32_t mNextBlock;
      int32_t mPrevBlock;
    };
    nsTHashtable<Entry> mEntries;

    // The index of the first block in the list, or -1 if the list is empty.
    int32_t mFirstBlock;
    // The number of blocks in the list.
    int32_t mCount;
  };

  // Returns the end of the bytes starting at the given offset
  // which are in cache.
  // This method assumes that the cache monitor is held and can be called on
  // any thread.
  int64_t GetCachedDataEndInternal(int64_t aOffset);
  // Returns the offset of the first byte of cached data at or after aOffset,
  // or -1 if there is no such cached data.
  // This method assumes that the cache monitor is held and can be called on
  // any thread.
  int64_t GetNextCachedDataInternal(int64_t aOffset);
  // Writes |mPartialBlock| to disk.
  // Used by |NotifyDataEnded| and |FlushPartialBlock|.
  // If |aNotifyAll| is true, this function will wake up readers who may be
  // waiting on the media cache monitor. Called on the main thread only.
  void FlushPartialBlockInternal(bool aNotify, ReentrantMonitorAutoEnter& aReentrantMonitor);
  // A helper function to do the work of closing the stream. Assumes
  // that the cache monitor is held. Main thread only.
  // aReentrantMonitor is the nsAutoReentrantMonitor wrapper holding the cache monitor.
  // This is used to NotifyAll to wake up threads that might be
  // blocked on reading from this stream.
  void CloseInternal(ReentrantMonitorAutoEnter& aReentrantMonitor);
  // Update mPrincipal given that data has been received from aPrincipal
  bool UpdatePrincipal(nsIPrincipal* aPrincipal);

  // These fields are main-thread-only.
  ChannelMediaResource*  mClient;
  nsCOMPtr<nsIPrincipal> mPrincipal;
  // Set to true when Init or InitAsClone has been called
  bool                   mInitialized;
  // Set to true when MediaCache::Update() has finished while this stream
  // was present.
  bool                   mHasHadUpdate;
  // Set to true when the stream has been closed either explicitly or
  // due to an internal cache error
  bool                   mClosed;
  // True if CacheClientNotifyDataEnded has been called for this stream.
  bool                   mDidNotifyDataEnded;

  // The following fields must be written holding the cache's monitor and
  // only on the main thread, thus can be read either on the main thread
  // or while holding the cache's monitor.

  // This is a unique ID representing the resource we're loading.
  // All streams with the same mResourceID are loading the same
  // underlying resource and should share data.
  int64_t mResourceID;
  // The last reported seekability state for the underlying channel
  bool mIsTransportSeekable;
  // True if the cache has suspended our channel because the cache is
  // full and the priority of the data that would be received is lower
  // than the priority of the data already in the cache
  bool mCacheSuspended;
  // True if the channel ended and we haven't seeked it again.
  bool mChannelEnded;
  // The offset where the next data from the channel will arrive
  int64_t      mChannelOffset;
  // The reported or discovered length of the data, or -1 if nothing is
  // known
  int64_t      mStreamLength;

  // The following fields are protected by the cache's monitor can can be written
  // by any thread.

  // The offset where the reader is positioned in the stream
  int64_t           mStreamOffset;
  // For each block in the stream data, maps to the cache entry for the
  // block, or -1 if the block is not cached.
  nsTArray<int32_t> mBlocks;
  // The list of read-ahead blocks, ordered by stream offset; the first
  // block is the earliest in the stream (so the last block will be the
  // least valuable).
  BlockList         mReadaheadBlocks;
  // The list of metadata blocks; the first block is the most recently used
  BlockList         mMetadataBlocks;
  // The list of played-back blocks; the first block is the most recently used
  BlockList         mPlayedBlocks;
  // The last reported estimate of the decoder's playback rate
  uint32_t          mPlaybackBytesPerSecond;
  // The number of times this stream has been Pinned without a
  // corresponding Unpin
  uint32_t          mPinCount;
  // The status used when we did CacheClientNotifyDataEnded. Only valid
  // when mDidNotifyDataEnded is true.
  nsresult          mNotifyDataEndedStatus;
  // The last reported read mode
  ReadMode          mCurrentMode;
  // True if some data in mPartialBlockBuffer has been read as metadata
  bool              mMetadataInPartialBlockBuffer;

  // The following field is protected by the cache's monitor but are
  // only written on the main thread.

  // Data received for the block containing mChannelOffset. Data needs
  // to wait here so we can write back a complete block. The first
  // mChannelOffset%BLOCK_SIZE bytes have been filled in with good data,
  // the rest are garbage.
  // Use int64_t so that the data is well-aligned.
  // Heap allocate this buffer since the exact power-of-2 will cause allocation
  // slop when combined with the rest of the object members.
  UniquePtr<int64_t[]> mPartialBlockBuffer;
};

} // namespace mozilla

#endif