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