--- a/dom/media/MediaCache.cpp
+++ b/dom/media/MediaCache.cpp
@@ -14,16 +14,17 @@
#include "mozilla/Logging.h"
#include "mozilla/Preferences.h"
#include "FileBlockCache.h"
#include "nsIObserverService.h"
#include "nsISeekableStream.h"
#include "nsIPrincipal.h"
#include "mozilla/Attributes.h"
#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
#include "mozilla/Telemetry.h"
#include <algorithm>
namespace mozilla {
#undef LOG
#undef LOGI
LazyLogModule gMediaCacheLog("MediaCache");
@@ -64,48 +65,70 @@ static const uint32_t FREE_BLOCK_SCAN_LI
// There is at most one media cache (although that could quite easily be
// relaxed if we wanted to manage multiple caches with independent
// size limits).
static MediaCache* gMediaCache;
class MediaCacheFlusher final : public nsIObserver,
public nsSupportsWeakReference
{
- MediaCacheFlusher() {}
- ~MediaCacheFlusher();
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
- static void Init();
+ static void RegisterMediaCache(MediaCache* aMediaCache);
+ static void UnregisterMediaCache(MediaCache* aMediaCache);
+
+private:
+ MediaCacheFlusher() {}
+ ~MediaCacheFlusher() {}
+
+ // Singleton instance created when a first MediaCache is registered, and
+ // released when the last MediaCache is unregistered.
+ // The observer service will keep a weak reference to it, for notifications.
+ static StaticRefPtr<MediaCacheFlusher> gMediaCacheFlusher;
+
+ nsTArray<MediaCache*> mMediaCaches;
};
-static MediaCacheFlusher* gMediaCacheFlusher;
+/* static */ StaticRefPtr<MediaCacheFlusher>
+ MediaCacheFlusher::gMediaCacheFlusher;
NS_IMPL_ISUPPORTS(MediaCacheFlusher, nsIObserver, nsISupportsWeakReference)
-MediaCacheFlusher::~MediaCacheFlusher()
+/* static */ void
+MediaCacheFlusher::RegisterMediaCache(MediaCache* aMediaCache)
{
- gMediaCacheFlusher = nullptr;
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
+ if (!gMediaCacheFlusher) {
+ gMediaCacheFlusher = new MediaCacheFlusher();
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(
+ gMediaCacheFlusher, "last-pb-context-exited", true);
+ observerService->AddObserver(
+ gMediaCacheFlusher, "cacheservice:empty-cache", true);
+ }
+ }
+
+ gMediaCacheFlusher->mMediaCaches.AppendElement(aMediaCache);
}
-void MediaCacheFlusher::Init()
+/* static */ void
+MediaCacheFlusher::UnregisterMediaCache(MediaCache* aMediaCache)
{
- if (gMediaCacheFlusher) {
- return;
- }
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
- gMediaCacheFlusher = new MediaCacheFlusher();
- NS_ADDREF(gMediaCacheFlusher);
+ gMediaCacheFlusher->mMediaCaches.RemoveElement(aMediaCache);
- nsCOMPtr<nsIObserverService> observerService =
- mozilla::services::GetObserverService();
- if (observerService) {
- observerService->AddObserver(gMediaCacheFlusher, "last-pb-context-exited", true);
- observerService->AddObserver(gMediaCacheFlusher, "cacheservice:empty-cache", true);
+ if (gMediaCacheFlusher->mMediaCaches.Length() == 0) {
+ gMediaCacheFlusher = nullptr;
}
}
class MediaCache {
public:
friend class MediaCacheStream::BlockList;
typedef MediaCacheStream::BlockList BlockList;
static const int64_t BLOCK_SIZE = MediaCacheStream::BLOCK_SIZE;
@@ -113,18 +136,20 @@ public:
MediaCache() : mNextResourceID(1),
mReentrantMonitor("MediaCache.mReentrantMonitor"),
mUpdateQueued(false)
#ifdef DEBUG
, mInUpdate(false)
#endif
{
MOZ_COUNT_CTOR(MediaCache);
+ MediaCacheFlusher::RegisterMediaCache(this);
}
~MediaCache() {
+ MediaCacheFlusher::UnregisterMediaCache(this);
NS_ASSERTION(mStreams.IsEmpty(), "Stream(s) still open!");
Truncate();
NS_ASSERTION(mIndex.Length() == 0, "Blocks leaked?");
if (mFileCache) {
mFileCache->Close();
mFileCache = nullptr;
}
LOG("MediaCache::~MediaCache(this=%p) MEDIACACHE_WATERMARK_KB=%u",
@@ -145,26 +170,25 @@ public:
// then the cache is still in a semi-valid state; mFD will be null,
// so all I/O on the cache file will fail.
nsresult Init();
// Shut down the global cache if it's no longer needed. We shut down
// the cache as soon as there are no streams. This means that during
// normal operation we are likely to start up the cache and shut it down
// many times, but that's OK since starting it up is cheap and
// shutting it down cleans things up and releases disk space.
- static void MaybeShutdown();
+ void MaybeShutdown();
// Brutally flush the cache contents. Main thread only.
- static void Flush();
- void FlushInternal();
+ void Flush();
// Close all streams associated with private browsing windows. This will
// also remove the blocks from the cache since we don't want to leave any
// traces when PB is done.
- static void CloseStreamsForPrivateBrowsing();
+ void CloseStreamsForPrivateBrowsing();
// Cache-file access methods. These are the lowest-level cache methods.
// mReentrantMonitor must be held; these can be called on any thread.
// This can return partial reads.
// Note mReentrantMonitor will be dropped while doing IO. The caller need
// to handle changes happening when the monitor is not held.
nsresult ReadCacheFile(int64_t aOffset, void* aData, int32_t aLength,
int32_t* aBytes);
@@ -375,22 +399,28 @@ protected:
#endif
// A list of resource IDs to notify about the change in suspended status.
nsTArray<int64_t> mSuspendedStatusToNotify;
};
NS_IMETHODIMP
MediaCacheFlusher::Observe(nsISupports *aSubject, char const *aTopic, char16_t const *aData)
{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+
if (strcmp(aTopic, "last-pb-context-exited") == 0) {
- MediaCache::CloseStreamsForPrivateBrowsing();
+ for (MediaCache* mc : mMediaCaches) {
+ mc->CloseStreamsForPrivateBrowsing();
+ }
return NS_OK;
}
if (strcmp(aTopic, "cacheservice:empty-cache") == 0) {
- MediaCache::Flush();
+ for (MediaCache* mc : mMediaCaches) {
+ mc->Flush();
+ }
return NS_OK;
}
return NS_OK;
}
MediaCacheStream::MediaCacheStream(ChannelMediaResource* aClient,
bool aIsPrivateBrowsing)
: mClient(aClient),
@@ -601,35 +631,23 @@ MediaCache::Init()
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
NS_ASSERTION(!mFileCache, "Cache file already open?");
mFileCache = new FileBlockCache();
nsresult rv = mFileCache->Init();
NS_ENSURE_SUCCESS(rv,rv);
- MediaCacheFlusher::Init();
-
return NS_OK;
}
void
MediaCache::Flush()
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
-
- if (!gMediaCache)
- return;
-
- gMediaCache->FlushInternal();
-}
-
-void
-MediaCache::FlushInternal()
-{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
for (uint32_t blockIndex = 0; blockIndex < mIndex.Length(); ++blockIndex) {
FreeBlock(blockIndex);
}
// Truncate file, close it, and reopen
Truncate();
@@ -640,42 +658,38 @@ MediaCache::FlushInternal()
}
Init();
}
void
MediaCache::CloseStreamsForPrivateBrowsing()
{
MOZ_ASSERT(NS_IsMainThread());
- if (!gMediaCache) {
- return;
- }
- for (auto& s : gMediaCache->mStreams) {
+ for (MediaCacheStream* s : mStreams) {
if (s->mIsPrivateBrowsing) {
s->Close();
}
}
}
void
MediaCache::MaybeShutdown()
{
NS_ASSERTION(NS_IsMainThread(),
"MediaCache::MaybeShutdown called on non-main thread");
- if (!gMediaCache->mStreams.IsEmpty()) {
+ if (!mStreams.IsEmpty()) {
// Don't shut down yet, streams are still alive
return;
}
// Since we're on the main thread, no-one is going to add a new stream
// while we shut down.
// This function is static so we don't have to delete 'this'.
delete gMediaCache;
gMediaCache = nullptr;
- NS_IF_RELEASE(gMediaCacheFlusher);
}
static void
InitMediaCache()
{
if (gMediaCache)
return;
@@ -1969,17 +1983,17 @@ MediaCacheStream::NotifyChannelRecreated
MediaCacheStream::~MediaCacheStream()
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
NS_ASSERTION(!mPinCount, "Unbalanced Pin");
if (gMediaCache) {
NS_ASSERTION(mClosed, "Stream was not closed");
gMediaCache->ReleaseStream(this);
- MediaCache::MaybeShutdown();
+ gMediaCache->MaybeShutdown();
}
uint32_t lengthKb = uint32_t(
std::min(std::max(mStreamLength, int64_t(0)) / 1024, int64_t(UINT32_MAX)));
LOG("MediaCacheStream::~MediaCacheStream(this=%p) "
"MEDIACACHESTREAM_LENGTH_KB=%" PRIu32,
this,
lengthKb);