1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
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 #ifndef MediaCache_h_
8 #define MediaCache_h_
9 
10 #include "DecoderDoctorLogger.h"
11 #include "Intervals.h"
12 #include "mozilla/Result.h"
13 #include "mozilla/UniquePtr.h"
14 #include "mozilla/dom/MediaDebugInfoBinding.h"
15 #include "nsCOMPtr.h"
16 #include "nsHashKeys.h"
17 #include "nsTArray.h"
18 #include "nsTHashtable.h"
19 
20 #include "MediaChannelStatistics.h"
21 
22 class nsIEventTarget;
23 class nsIPrincipal;
24 
25 namespace mozilla {
26 // defined in MediaResource.h
27 class ChannelMediaResource;
28 typedef media::IntervalSet<int64_t> MediaByteRangeSet;
29 class MediaResource;
30 class MonitorAutoLock;
31 
32 /**
33  * Media applications want fast, "on demand" random access to media data,
34  * for pausing, seeking, etc. But we are primarily interested
35  * in transporting media data using HTTP over the Internet, which has
36  * high latency to open a connection, requires a new connection for every
37  * seek, may not even support seeking on some connections (especially
38  * live streams), and uses a push model --- data comes from the server
39  * and you don't have much control over the rate. Also, transferring data
40  * over the Internet can be slow and/or unpredictable, so we want to read
41  * ahead to buffer and cache as much data as possible.
42  *
43  * The job of the media cache is to resolve this impedance mismatch.
44  * The media cache reads data from Necko channels into file-backed storage,
45  * and offers a random-access file-like API to the stream data
46  * (MediaCacheStream). Along the way it solves several problems:
47  * -- The cache intelligently reads ahead to prefetch data that may be
48  * needed in the future
49  * -- The size of the cache is bounded so that we don't fill up
50  * storage with read-ahead data
51  * -- Cache replacement is managed globally so that the most valuable
52  * data (across all streams) is retained
53  * -- The cache can suspend Necko channels temporarily when their data is
54  * not wanted (yet)
55  * -- The cache translates file-like seek requests to HTTP seeks,
56  * including optimizations like not triggering a new seek if it would
57  * be faster to just keep reading until we reach the seek point. The
58  * "seek to EOF" idiom to determine file size is also handled efficiently
59  * (seeking to EOF and then seeking back to the previous offset does not
60  * trigger any Necko activity)
61  * -- The cache also handles the case where the server does not support
62  * seeking
63  * -- Necko can only send data to the main thread, but MediaCacheStream
64  * can distribute data to any thread
65  * -- The cache exposes APIs so clients can detect what data is
66  * currently held
67  *
68  * Note that although HTTP is the most important transport and we only
69  * support transport-level seeking via HTTP byte-ranges, the media cache
70  * works with any kind of Necko channels and provides random access to
71  * cached data even for, e.g., FTP streams.
72  *
73  * The media cache is not persistent. It does not currently allow
74  * data from one load to be used by other loads, either within the same
75  * browser session or across browser sessions. The media cache file
76  * is marked "delete on close" so it will automatically disappear in the
77  * event of a browser crash or shutdown.
78  *
79  * The media cache is block-based. Streams are divided into blocks of a
80  * fixed size (currently 4K) and we cache blocks. A single cache contains
81  * blocks for all streams.
82  *
83  * The cache size is controlled by the media.cache_size preference
84  * (which is in KB). The default size is 500MB.
85  *
86  * The replacement policy predicts a "time of next use" for each block
87  * in the cache. When we need to free a block, the block with the latest
88  * "time of next use" will be evicted. Blocks are divided into
89  * different classes, each class having its own predictor:
90  * FREE_BLOCK: these blocks are effectively infinitely far in the future;
91  * a free block will always be chosen for replacement before other classes
92  * of blocks.
93  * METADATA_BLOCK: these are blocks that contain data that has been read
94  * by the decoder in "metadata mode", e.g. while the decoder is searching
95  * the stream during a seek operation. These blocks are managed with an
96  * LRU policy; the "time of next use" is predicted to be as far in the
97  * future as the last use was in the past.
98  * PLAYED_BLOCK: these are blocks that have not been read in "metadata
99  * mode", and contain data behind the current decoder read point. (They
100  * may not actually have been read by the decoder, if the decoder seeked
101  * forward.) These blocks are managed with an LRU policy except that we add
102  * REPLAY_DELAY seconds of penalty to their predicted "time of next use",
103  * to reflect the uncertainty about whether replay will actually happen
104  * or not.
105  * READAHEAD_BLOCK: these are blocks that have not been read in
106  * "metadata mode" and that are entirely ahead of the current decoder
107  * read point. (They may actually have been read by the decoder in the
108  * past if the decoder has since seeked backward.) We predict the
109  * time of next use for these blocks by assuming steady playback and
110  * dividing the number of bytes between the block and the current decoder
111  * read point by the decoder's estimate of its playback rate in bytes
112  * per second. This ensures that the blocks farthest ahead are considered
113  * least valuable.
114  * For efficient prediction of the "latest time of next use", we maintain
115  * linked lists of blocks in each class, ordering blocks by time of
116  * next use. READAHEAD_BLOCKS have one linked list per stream, since their
117  * time of next use depends on stream parameters, but the other lists
118  * are global.
119  *
120  * A block containing a current decoder read point can contain data
121  * both behind and ahead of the read point. It will be classified as a
122  * PLAYED_BLOCK but we will give it special treatment so it is never
123  * evicted --- it actually contains the highest-priority readahead data
124  * as well as played data.
125  *
126  * "Time of next use" estimates are also used for flow control. When
127  * reading ahead we can predict the time of next use for the data that
128  * will be read. If the predicted time of next use is later then the
129  * prediction for all currently cached blocks, and the cache is full, then
130  * we should suspend reading from the Necko channel.
131  *
132  * Unfortunately suspending the Necko channel can't immediately stop the
133  * flow of data from the server. First our desire to suspend has to be
134  * transmitted to the server (in practice, Necko stops reading from the
135  * socket, which causes the kernel to shrink its advertised TCP receive
136  * window size to zero). Then the server can stop sending the data, but
137  * we will receive data roughly corresponding to the product of the link
138  * bandwidth multiplied by the round-trip latency. We deal with this by
139  * letting the cache overflow temporarily and then trimming it back by
140  * moving overflowing blocks back into the body of the cache, replacing
141  * less valuable blocks as they become available. We try to avoid simply
142  * discarding overflowing readahead data.
143  *
144  * All changes to the actual contents of the cache happen on the main
145  * thread, since that's where Necko's notifications happen.
146  *
147  * The media cache maintains at most one Necko channel for each stream.
148  * (In the future it might be advantageous to relax this, e.g. so that a
149  * seek to near the end of the file can happen without disturbing
150  * the loading of data from the beginning of the file.) The Necko channel
151  * is managed through ChannelMediaResource; MediaCache does not
152  * depend on Necko directly.
153  *
154  * Every time something changes that might affect whether we want to
155  * read from a Necko channel, or whether we want to seek on the Necko
156  * channel --- such as data arriving or data being consumed by the
157  * decoder --- we asynchronously trigger MediaCache::Update on the main
158  * thread. That method implements most cache policy. It evaluates for
159  * each stream whether we want to suspend or resume the stream and what
160  * offset we should seek to, if any. It is also responsible for trimming
161  * back the cache size to its desired limit by moving overflowing blocks
162  * into the main part of the cache.
163  *
164  * Streams can be opened in non-seekable mode. In non-seekable mode,
165  * the cache will only call ChannelMediaResource::CacheClientSeek with
166  * a 0 offset. The cache tries hard not to discard readahead data
167  * for non-seekable streams, since that could trigger a potentially
168  * disastrous re-read of the entire stream. It's up to cache clients
169  * to try to avoid requesting seeks on such streams.
170  *
171  * MediaCache has a single internal monitor for all synchronization.
172  * This is treated as the lowest level monitor in the media code. So,
173  * we must not acquire any MediaDecoder locks or MediaResource locks
174  * while holding the MediaCache lock. But it's OK to hold those locks
175  * and then get the MediaCache lock.
176  *
177  * MediaCache associates a principal with each stream. CacheClientSeek
178  * can trigger new HTTP requests; due to redirects to other domains,
179  * each HTTP load can return data with a different principal. This
180  * principal must be passed to NotifyDataReceived, and MediaCache
181  * will detect when different principals are associated with data in the
182  * same stream, and replace them with a null principal.
183  */
184 class MediaCache;
185 
186 DDLoggedTypeDeclName(MediaCacheStream);
187 
188 /**
189  * If the cache fails to initialize then Init will fail, so nonstatic
190  * methods of this class can assume gMediaCache is non-null.
191  *
192  * This class can be directly embedded as a value.
193  */
194 class MediaCacheStream : public DecoderDoctorLifeLogger<MediaCacheStream> {
195   using AutoLock = MonitorAutoLock;
196 
197  public:
198   // This needs to be a power of two
199   static const int64_t BLOCK_SIZE = 32768;
200 
201   enum ReadMode { MODE_METADATA, MODE_PLAYBACK };
202 
203   // aClient provides the underlying transport that cache will use to read
204   // data for this stream.
205   MediaCacheStream(ChannelMediaResource* aClient, bool aIsPrivateBrowsing);
206   ~MediaCacheStream();
207 
208   // Set up this stream with the cache. Can fail on OOM.
209   // aContentLength is the content length if known, otherwise -1.
210   // Exactly one of InitAsClone or Init must be called before any other method
211   // on this class. Does nothing if already initialized.
212   nsresult Init(int64_t aContentLength);
213 
214   // Set up this stream with the cache, assuming it's for the same data
215   // as the aOriginal stream.
216   // Exactly one of InitAsClone or Init must be called before any other method
217   // on this class.
218   void InitAsClone(MediaCacheStream* aOriginal);
219 
220   nsISerialEventTarget* OwnerThread() const;
221 
222   // These are called on the main thread.
223   // This must be called (and resolve) before the ChannelMediaResource
224   // used to create this MediaCacheStream is deleted.
225   RefPtr<GenericPromise> Close();
226   // This returns true when the stream has been closed.
IsClosed(AutoLock &)227   bool IsClosed(AutoLock&) const { return mClosed; }
228   // Returns true when this stream is can be shared by a new resource load.
229   // Called on the main thread only.
IsAvailableForSharing()230   bool IsAvailableForSharing() const { return !mIsPrivateBrowsing; }
231 
232   // These callbacks are called on the main thread by the client
233   // when data has been received via the channel.
234 
235   // Notifies the cache that a load has begun. We pass the offset
236   // because in some cases the offset might not be what the cache
237   // requested. In particular we might unexpectedly start providing
238   // data at offset 0. This need not be called if the offset is the
239   // offset that the cache requested in
240   // ChannelMediaResource::CacheClientSeek. This can be called at any
241   // time by the client, not just after a CacheClientSeek.
242   //
243   // aSeekable tells us whether the stream is seekable or not. Non-seekable
244   // streams will always pass 0 for aOffset to CacheClientSeek. This should only
245   // be called while the stream is at channel offset 0. Seekability can
246   // change during the lifetime of the MediaCacheStream --- every time
247   // we do an HTTP load the seekability may be different (and sometimes
248   // is, in practice, due to the effects of caching proxies).
249   //
250   // aLength tells the cache what the server said the data length is going to
251   // be. The actual data length may be greater (we receive more data than
252   // specified) or smaller (the stream ends before we reach the given
253   // length), because servers can lie. The server's reported data length
254   // *and* the actual data length can even vary over time because a
255   // misbehaving server may feed us a different stream after each seek
256   // operation. So this is really just a hint. The cache may however
257   // stop reading (suspend the channel) when it thinks we've read all the
258   // data available based on an incorrect reported length. Seeks relative
259   // EOF also depend on the reported length if we haven't managed to
260   // read the whole stream yet.
261   void NotifyDataStarted(uint32_t aLoadID, int64_t aOffset, bool aSeekable,
262                          int64_t aLength);
263   // Notifies the cache that data has been received. The stream already
264   // knows the offset because data is received in sequence and
265   // the starting offset is known via NotifyDataStarted or because
266   // the cache requested the offset in
267   // ChannelMediaResource::CacheClientSeek, or because it defaulted to 0.
268   void NotifyDataReceived(uint32_t aLoadID, uint32_t aCount,
269                           const uint8_t* aData);
270 
271   // Set the load ID so the following NotifyDataEnded() call can work properly.
272   // Used in some rare cases where NotifyDataEnded() is called without the
273   // preceding NotifyDataStarted().
274   void NotifyLoadID(uint32_t aLoadID);
275 
276   // Notifies the cache that the channel has closed with the given status.
277   void NotifyDataEnded(uint32_t aLoadID, nsresult aStatus);
278 
279   // Notifies the stream that the suspend status of the client has changed.
280   // Main thread only.
281   void NotifyClientSuspended(bool aSuspended);
282 
283   // Notifies the stream to resume download at the current offset.
284   void NotifyResume();
285 
286   // These methods can be called on any thread.
287   // Cached blocks associated with this stream will not be evicted
288   // while the stream is pinned.
289   void Pin();
290   void Unpin();
291   // See comments above for NotifyDataStarted about how the length
292   // can vary over time. Returns -1 if no length is known. Returns the
293   // reported length if we haven't got any better information. If
294   // the stream ended normally we return the length we actually got.
295   // If we've successfully read data beyond the originally reported length,
296   // we return the end of the data we've read.
297   int64_t GetLength() const;
298   // Return the length and offset where next channel data will write to. Main
299   // thread only.
300   // This method should be removed as part of bug 1464045.
301   struct LengthAndOffset {
302     int64_t mLength;
303     int64_t mOffset;
304   };
305   LengthAndOffset GetLengthAndOffset() const;
306   // Returns the unique resource ID. Call only on the main thread or while
307   // holding the media cache lock.
GetResourceID()308   int64_t GetResourceID() { return mResourceID; }
309   // Returns the end of the bytes starting at the given offset
310   // which are in cache.
311   int64_t GetCachedDataEnd(int64_t aOffset);
312   // Returns the offset of the first byte of cached data at or after aOffset,
313   // or -1 if there is no such cached data.
314   int64_t GetNextCachedData(int64_t aOffset);
315   // Fills aRanges with the ByteRanges representing the data which is currently
316   // cached. Locks the media cache while running, to prevent any ranges
317   // growing. The stream should be pinned while this runs and while its results
318   // are used, to ensure no data is evicted.
319   nsresult GetCachedRanges(MediaByteRangeSet& aRanges);
320 
321   double GetDownloadRate(bool* aIsReliable);
322 
323   // Reads from buffered data only. Will fail if not all data to be read is
324   // in the cache. Will not mark blocks as read. Can be called from the main
325   // thread. It's the caller's responsibility to wrap the call in a pin/unpin,
326   // and also to check that the range they want is cached before calling this.
327   nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount);
328 
329   // IsDataCachedToEndOfStream returns true if all the data from
330   // aOffset to the end of the stream (the server-reported end, if the
331   // real end is not known) is in cache. If we know nothing about the
332   // end of the stream, this returns false.
333   bool IsDataCachedToEndOfStream(int64_t aOffset);
334   // The mode is initially MODE_METADATA.
335   void SetReadMode(ReadMode aMode);
336   // This is the client's estimate of the playback rate assuming
337   // the media plays continuously. The cache can't guess this itself
338   // because it doesn't know when the decoder was paused, buffering, etc.
339   // Do not pass zero.
340   void SetPlaybackRate(uint32_t aBytesPerSecond);
341 
342   // Returns true when all streams for this resource are suspended or their
343   // channel has ended.
344   bool AreAllStreamsForResourceSuspended(AutoLock&);
345 
346   // These methods must be called on a different thread from the main
347   // thread. They should always be called on the same thread for a given
348   // stream.
349   // *aBytes gets the number of bytes that were actually read. This can
350   // be less than aCount. If the first byte of data is not in the cache,
351   // this will block until the data is available or the stream is
352   // closed, otherwise it won't block.
353   nsresult Read(AutoLock&, char* aBuffer, uint32_t aCount, uint32_t* aBytes);
354   // Seeks to aOffset in the stream then performs a Read operation. See
355   // 'Read' for argument and return details.
356   nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount,
357                   uint32_t* aBytes);
358 
359   void ThrottleReadahead(bool bThrottle);
360 
361   size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
362 
363   void GetDebugInfo(dom::MediaCacheStreamDebugInfo& aInfo);
364 
365  private:
366   friend class MediaCache;
367 
368   /**
369    * A doubly-linked list of blocks. Add/Remove/Get methods are all
370    * constant time. We declare this here so that a stream can contain a
371    * BlockList of its read-ahead blocks. Blocks are referred to by index
372    * into the MediaCache::mIndex array.
373    *
374    * Blocks can belong to more than one list at the same time, because
375    * the next/prev pointers are not stored in the block.
376    */
377   class BlockList {
378    public:
BlockList()379     BlockList() : mFirstBlock(-1), mCount(0) {}
~BlockList()380     ~BlockList() {
381       NS_ASSERTION(mFirstBlock == -1 && mCount == 0,
382                    "Destroying non-empty block list");
383     }
384     void AddFirstBlock(int32_t aBlock);
385     void AddAfter(int32_t aBlock, int32_t aBefore);
386     void RemoveBlock(int32_t aBlock);
387     // Returns the first block in the list, or -1 if empty
GetFirstBlock()388     int32_t GetFirstBlock() const { return mFirstBlock; }
389     // Returns the last block in the list, or -1 if empty
390     int32_t GetLastBlock() const;
391     // Returns the next block in the list after aBlock or -1 if
392     // aBlock is the last block
393     int32_t GetNextBlock(int32_t aBlock) const;
394     // Returns the previous block in the list before aBlock or -1 if
395     // aBlock is the first block
396     int32_t GetPrevBlock(int32_t aBlock) const;
IsEmpty()397     bool IsEmpty() const { return mFirstBlock < 0; }
GetCount()398     int32_t GetCount() const { return mCount; }
399     // The contents of aBlockIndex1 and aBlockIndex2 have been swapped
400     void NotifyBlockSwapped(int32_t aBlockIndex1, int32_t aBlockIndex2);
401 #ifdef DEBUG
402     // Verify linked-list invariants
403     void Verify();
404 #else
Verify()405     void Verify() {}
406 #endif
407 
408     size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
409 
410    private:
411     struct Entry : public nsUint32HashKey {
EntryEntry412       explicit Entry(KeyTypePointer aKey)
413           : nsUint32HashKey(aKey), mNextBlock(0), mPrevBlock(0) {}
EntryEntry414       Entry(const Entry& toCopy)
415           : nsUint32HashKey(&toCopy.GetKey()),
416             mNextBlock(toCopy.mNextBlock),
417             mPrevBlock(toCopy.mPrevBlock) {}
418 
419       int32_t mNextBlock;
420       int32_t mPrevBlock;
421     };
422     nsTHashtable<Entry> mEntries;
423 
424     // The index of the first block in the list, or -1 if the list is empty.
425     int32_t mFirstBlock;
426     // The number of blocks in the list.
427     int32_t mCount;
428   };
429 
430   // Read data from the partial block and return the number of bytes read
431   // successfully. 0 if aOffset is not an offset in the partial block or there
432   // is nothing to read.
433   uint32_t ReadPartialBlock(AutoLock&, int64_t aOffset, Span<char> aBuffer);
434 
435   // Read data from the cache block specified by aOffset. Return the number of
436   // bytes read successfully or an error code if any failure.
437   Result<uint32_t, nsresult> ReadBlockFromCache(AutoLock&, int64_t aOffset,
438                                                 Span<char> aBuffer,
439                                                 bool aNoteBlockUsage = false);
440 
441   // Non-main thread only.
442   nsresult Seek(AutoLock&, int64_t aOffset);
443 
444   // Returns the end of the bytes starting at the given offset
445   // which are in cache.
446   // This method assumes that the cache monitor is held and can be called on
447   // any thread.
448   int64_t GetCachedDataEndInternal(AutoLock&, int64_t aOffset);
449   // Returns the offset of the first byte of cached data at or after aOffset,
450   // or -1 if there is no such cached data.
451   // This method assumes that the cache monitor is held and can be called on
452   // any thread.
453   int64_t GetNextCachedDataInternal(AutoLock&, int64_t aOffset);
454   // Used by |NotifyDataEnded| to write |mPartialBlock| to disk.
455   // If |aNotifyAll| is true, this function will wake up readers who may be
456   // waiting on the media cache monitor. Called on the media cache thread only.
457   void FlushPartialBlockInternal(AutoLock&, bool aNotifyAll);
458 
459   void NotifyDataStartedInternal(uint32_t aLoadID, int64_t aOffset,
460                                  bool aSeekable, int64_t aLength);
461 
462   void NotifyDataEndedInternal(uint32_t aLoadID, nsresult aStatus);
463 
464   void UpdateDownloadStatistics(AutoLock&);
465 
466   void CloseInternal(AutoLock&);
467   void InitAsCloneInternal(MediaCacheStream* aOriginal);
468 
469   // Instance of MediaCache to use with this MediaCacheStream.
470   RefPtr<MediaCache> mMediaCache;
471 
472   ChannelMediaResource* const mClient;
473 
474   // The following fields must be written holding the cache's monitor and
475   // only on the main thread, thus can be read either on the main thread
476   // or while holding the cache's monitor.
477 
478   // Set to true when the stream has been closed either explicitly or
479   // due to an internal cache error
480   bool mClosed = false;
481   // This is a unique ID representing the resource we're loading.
482   // All streams with the same mResourceID are loading the same
483   // underlying resource and should share data.
484   // Initialized to 0 as invalid. Will be allocated a valid ID (always positive)
485   // from the cache.
486   int64_t mResourceID = 0;
487   // The last reported seekability state for the underlying channel
488   bool mIsTransportSeekable;
489   // True if the cache has suspended our channel because the cache is
490   // full and the priority of the data that would be received is lower
491   // than the priority of the data already in the cache
492   bool mCacheSuspended;
493   // True if the channel ended and we haven't seeked it again.
494   bool mChannelEnded;
495 
496   // The following fields are protected by the cache's monitor and can be
497   // written by any thread.
498 
499   // The reported or discovered length of the data, or -1 if nothing is known
500   int64_t mStreamLength = -1;
501   // The offset where the next data from the channel will arrive
502   int64_t mChannelOffset = 0;
503   // The offset where the reader is positioned in the stream
504   int64_t mStreamOffset;
505   // For each block in the stream data, maps to the cache entry for the
506   // block, or -1 if the block is not cached.
507   nsTArray<int32_t> mBlocks;
508   // The list of read-ahead blocks, ordered by stream offset; the first
509   // block is the earliest in the stream (so the last block will be the
510   // least valuable).
511   BlockList mReadaheadBlocks;
512   // The list of metadata blocks; the first block is the most recently used
513   BlockList mMetadataBlocks;
514   // The list of played-back blocks; the first block is the most recently used
515   BlockList mPlayedBlocks;
516   // The last reported estimate of the decoder's playback rate
517   uint32_t mPlaybackBytesPerSecond;
518   // The number of times this stream has been Pinned without a
519   // corresponding Unpin
520   uint32_t mPinCount;
521   // True if CacheClientNotifyDataEnded has been called for this stream.
522   bool mDidNotifyDataEnded = false;
523   // The status used when we did CacheClientNotifyDataEnded. Only valid
524   // when mDidNotifyDataEnded is true.
525   nsresult mNotifyDataEndedStatus;
526   // The last reported read mode
527   ReadMode mCurrentMode = MODE_METADATA;
528   // The load ID of the current channel. Used to check whether the data is
529   // coming from an old channel and should be discarded.
530   uint32_t mLoadID = 0;
531   // The seek target initiated by MediaCache. -1 if no seek is going on.
532   int64_t mSeekTarget = -1;
533 
534   bool mThrottleReadahead = false;
535 
536   // Data received for the block containing mChannelOffset. Data needs
537   // to wait here so we can write back a complete block. The first
538   // mChannelOffset%BLOCK_SIZE bytes have been filled in with good data,
539   // the rest are garbage.
540   // Heap allocate this buffer since the exact power-of-2 will cause allocation
541   // slop when combined with the rest of the object members.
542   // This partial buffer should always be read/write within the cache's monitor.
543   const UniquePtr<uint8_t[]> mPartialBlockBuffer =
544       MakeUnique<uint8_t[]>(BLOCK_SIZE);
545 
546   // True if associated with a private browsing window.
547   const bool mIsPrivateBrowsing;
548 
549   // True if the client is suspended. Accessed on the owner thread only.
550   bool mClientSuspended = false;
551 
552   MediaChannelStatistics mDownloadStatistics;
553 };
554 
555 }  // namespace mozilla
556 
557 #endif
558