/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * 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/. */ #include "nsCache.h" #include "nspr.h" #include "nsCacheEntry.h" #include "nsCacheEntryDescriptor.h" #include "nsCacheMetaData.h" #include "nsCacheRequest.h" #include "nsThreadUtils.h" #include "nsError.h" #include "nsCacheService.h" #include "nsCacheDevice.h" #include "nsHashKeys.h" using namespace mozilla; nsCacheEntry::nsCacheEntry(const nsACString& key, bool streamBased, nsCacheStoragePolicy storagePolicy) : mKey(key), mFetchCount(0), mLastFetched(0), mLastModified(0), mLastValidated(0), mExpirationTime(nsICache::NO_EXPIRATION_TIME), mFlags(0), mPredictedDataSize(-1), mDataSize(0), mCacheDevice(nullptr), mCustomDevice(nullptr), mData(nullptr), mRequestQ{}, mDescriptorQ{} { MOZ_COUNT_CTOR(nsCacheEntry); PR_INIT_CLIST(this); PR_INIT_CLIST(&mRequestQ); PR_INIT_CLIST(&mDescriptorQ); if (streamBased) MarkStreamBased(); SetStoragePolicy(storagePolicy); MarkPublic(); } nsCacheEntry::~nsCacheEntry() { MOZ_COUNT_DTOR(nsCacheEntry); if (mData) nsCacheService::ReleaseObject_Locked(mData, mEventTarget); } nsresult nsCacheEntry::Create(const char* key, bool streamBased, nsCacheStoragePolicy storagePolicy, nsCacheDevice* device, nsCacheEntry** result) { nsCacheEntry* entry = new nsCacheEntry(nsCString(key), streamBased, storagePolicy); entry->SetCacheDevice(device); *result = entry; return NS_OK; } void nsCacheEntry::Fetched() { mLastFetched = SecondsFromPRTime(PR_Now()); ++mFetchCount; MarkEntryDirty(); } const char* nsCacheEntry::GetDeviceID() { if (mCacheDevice) return mCacheDevice->GetDeviceID(); return nullptr; } void nsCacheEntry::TouchData() { mLastModified = SecondsFromPRTime(PR_Now()); MarkDataDirty(); } void nsCacheEntry::SetData(nsISupports* data) { if (mData) { nsCacheService::ReleaseObject_Locked(mData, mEventTarget); mData = nullptr; } if (data) { NS_ADDREF(mData = data); mEventTarget = GetCurrentEventTarget(); } } void nsCacheEntry::TouchMetaData() { mLastModified = SecondsFromPRTime(PR_Now()); MarkMetaDataDirty(); } /** * cache entry states * 0 descriptors (new entry) * 0 descriptors (existing, bound entry) * n descriptors (existing, bound entry) valid * n descriptors (existing, bound entry) not valid (wait until valid or * doomed) */ nsresult nsCacheEntry::RequestAccess(nsCacheRequest* request, nsCacheAccessMode* accessGranted) { nsresult rv = NS_OK; if (IsDoomed()) return NS_ERROR_CACHE_ENTRY_DOOMED; if (!IsInitialized()) { // brand new, unbound entry if (request->IsStreamBased()) MarkStreamBased(); MarkInitialized(); *accessGranted = request->AccessRequested() & nsICache::ACCESS_WRITE; NS_ASSERTION(*accessGranted, "new cache entry for READ-ONLY request"); PR_APPEND_LINK(request, &mRequestQ); return rv; } if (IsStreamData() != request->IsStreamBased()) { *accessGranted = nsICache::ACCESS_NONE; return request->IsStreamBased() ? NS_ERROR_CACHE_DATA_IS_NOT_STREAM : NS_ERROR_CACHE_DATA_IS_STREAM; } if (PR_CLIST_IS_EMPTY(&mDescriptorQ)) { // 1st descriptor for existing bound entry *accessGranted = request->AccessRequested(); if (*accessGranted & nsICache::ACCESS_WRITE) { MarkInvalid(); } else { MarkValid(); } } else { // nth request for existing, bound entry *accessGranted = request->AccessRequested() & ~nsICache::ACCESS_WRITE; if (!IsValid()) rv = NS_ERROR_CACHE_WAIT_FOR_VALIDATION; } PR_APPEND_LINK(request, &mRequestQ); return rv; } nsresult nsCacheEntry::CreateDescriptor(nsCacheRequest* request, nsCacheAccessMode accessGranted, nsICacheEntryDescriptor** result) { NS_ENSURE_ARG_POINTER(request && result); nsCacheEntryDescriptor* descriptor = new nsCacheEntryDescriptor(this, accessGranted); // XXX check request is on q PR_REMOVE_AND_INIT_LINK(request); // remove request regardless of success if (descriptor == nullptr) return NS_ERROR_OUT_OF_MEMORY; PR_APPEND_LINK(descriptor, &mDescriptorQ); CACHE_LOG_DEBUG((" descriptor %p created for request %p on entry %p\n", descriptor, request, this)); *result = do_AddRef(descriptor).take(); return NS_OK; } bool nsCacheEntry::RemoveRequest(nsCacheRequest* request) { // XXX if debug: verify this request belongs to this entry PR_REMOVE_AND_INIT_LINK(request); // return true if this entry should stay active return !((PR_CLIST_IS_EMPTY(&mRequestQ)) && (PR_CLIST_IS_EMPTY(&mDescriptorQ))); } bool nsCacheEntry::RemoveDescriptor(nsCacheEntryDescriptor* descriptor, bool* doomEntry) { NS_ASSERTION(descriptor->CacheEntry() == this, "### Wrong cache entry!!"); *doomEntry = descriptor->ClearCacheEntry(); PR_REMOVE_AND_INIT_LINK(descriptor); if (!PR_CLIST_IS_EMPTY(&mDescriptorQ)) return true; // stay active if we still have open descriptors if (PR_CLIST_IS_EMPTY(&mRequestQ)) return false; // no descriptors or requests, we can deactivate return true; // find next best request to give a descriptor to } void nsCacheEntry::DetachDescriptors() { nsCacheEntryDescriptor* descriptor = (nsCacheEntryDescriptor*)PR_LIST_HEAD(&mDescriptorQ); while (descriptor != &mDescriptorQ) { nsCacheEntryDescriptor* nextDescriptor = (nsCacheEntryDescriptor*)PR_NEXT_LINK(descriptor); descriptor->ClearCacheEntry(); PR_REMOVE_AND_INIT_LINK(descriptor); descriptor = nextDescriptor; } } void nsCacheEntry::GetDescriptors( nsTArray >& outDescriptors) { nsCacheEntryDescriptor* descriptor = (nsCacheEntryDescriptor*)PR_LIST_HEAD(&mDescriptorQ); while (descriptor != &mDescriptorQ) { nsCacheEntryDescriptor* nextDescriptor = (nsCacheEntryDescriptor*)PR_NEXT_LINK(descriptor); outDescriptors.AppendElement(descriptor); descriptor = nextDescriptor; } } /****************************************************************************** * nsCacheEntryInfo - for implementing about:cache *****************************************************************************/ NS_IMPL_ISUPPORTS(nsCacheEntryInfo, nsICacheEntryInfo) NS_IMETHODIMP nsCacheEntryInfo::GetClientID(nsACString& aClientID) { if (!mCacheEntry) { aClientID.Truncate(); return NS_ERROR_NOT_AVAILABLE; } return ClientIDFromCacheKey(*mCacheEntry->Key(), aClientID); } NS_IMETHODIMP nsCacheEntryInfo::GetDeviceID(nsACString& aDeviceID) { if (!mCacheEntry) { aDeviceID.Truncate(); return NS_ERROR_NOT_AVAILABLE; } aDeviceID.Assign(mCacheEntry->GetDeviceID()); return NS_OK; } NS_IMETHODIMP nsCacheEntryInfo::GetKey(nsACString& key) { if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; return ClientKeyFromCacheKey(*mCacheEntry->Key(), key); } NS_IMETHODIMP nsCacheEntryInfo::GetFetchCount(int32_t* fetchCount) { NS_ENSURE_ARG_POINTER(fetchCount); if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; *fetchCount = mCacheEntry->FetchCount(); return NS_OK; } NS_IMETHODIMP nsCacheEntryInfo::GetLastFetched(uint32_t* lastFetched) { NS_ENSURE_ARG_POINTER(lastFetched); if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; *lastFetched = mCacheEntry->LastFetched(); return NS_OK; } NS_IMETHODIMP nsCacheEntryInfo::GetLastModified(uint32_t* lastModified) { NS_ENSURE_ARG_POINTER(lastModified); if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; *lastModified = mCacheEntry->LastModified(); return NS_OK; } NS_IMETHODIMP nsCacheEntryInfo::GetExpirationTime(uint32_t* expirationTime) { NS_ENSURE_ARG_POINTER(expirationTime); if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; *expirationTime = mCacheEntry->ExpirationTime(); return NS_OK; } NS_IMETHODIMP nsCacheEntryInfo::GetDataSize(uint32_t* dataSize) { NS_ENSURE_ARG_POINTER(dataSize); if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; *dataSize = mCacheEntry->DataSize(); return NS_OK; } NS_IMETHODIMP nsCacheEntryInfo::IsStreamBased(bool* result) { NS_ENSURE_ARG_POINTER(result); if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; *result = mCacheEntry->IsStreamData(); return NS_OK; } /****************************************************************************** * nsCacheEntryHashTable *****************************************************************************/ const PLDHashTableOps nsCacheEntryHashTable::ops = {HashKey, MatchEntry, MoveEntry, ClearEntry}; nsCacheEntryHashTable::nsCacheEntryHashTable() : table(&ops, sizeof(nsCacheEntryHashTableEntry), kInitialTableLength), initialized(false) { MOZ_COUNT_CTOR(nsCacheEntryHashTable); } nsCacheEntryHashTable::~nsCacheEntryHashTable() { MOZ_COUNT_DTOR(nsCacheEntryHashTable); if (initialized) Shutdown(); } void nsCacheEntryHashTable::Init() { table.ClearAndPrepareForLength(kInitialTableLength); initialized = true; } void nsCacheEntryHashTable::Shutdown() { if (initialized) { table.ClearAndPrepareForLength(kInitialTableLength); initialized = false; } } nsCacheEntry* nsCacheEntryHashTable::GetEntry(const nsCString* key) const { NS_ASSERTION(initialized, "nsCacheEntryHashTable not initialized"); if (!initialized) return nullptr; PLDHashEntryHdr* hashEntry = table.Search(key); return hashEntry ? ((nsCacheEntryHashTableEntry*)hashEntry)->cacheEntry : nullptr; } nsresult nsCacheEntryHashTable::AddEntry(nsCacheEntry* cacheEntry) { PLDHashEntryHdr* hashEntry; NS_ASSERTION(initialized, "nsCacheEntryHashTable not initialized"); if (!initialized) return NS_ERROR_NOT_INITIALIZED; if (!cacheEntry) return NS_ERROR_NULL_POINTER; hashEntry = table.Add(&(cacheEntry->mKey), fallible); if (!hashEntry) return NS_ERROR_FAILURE; NS_ASSERTION(((nsCacheEntryHashTableEntry*)hashEntry)->cacheEntry == nullptr, "### nsCacheEntryHashTable::AddEntry - entry already used"); ((nsCacheEntryHashTableEntry*)hashEntry)->cacheEntry = cacheEntry; return NS_OK; } void nsCacheEntryHashTable::RemoveEntry(nsCacheEntry* cacheEntry) { NS_ASSERTION(initialized, "nsCacheEntryHashTable not initialized"); NS_ASSERTION(cacheEntry, "### cacheEntry == nullptr"); if (!initialized) return; // NS_ERROR_NOT_INITIALIZED #if DEBUG // XXX debug code to make sure we have the entry we're trying to remove nsCacheEntry* check = GetEntry(&(cacheEntry->mKey)); NS_ASSERTION(check == cacheEntry, "### Attempting to remove unknown cache entry!!!"); #endif table.Remove(&(cacheEntry->mKey)); } PLDHashTable::Iterator nsCacheEntryHashTable::Iter() { return PLDHashTable::Iterator(&table); } /** * hash table operation callback functions */ PLDHashNumber nsCacheEntryHashTable::HashKey(const void* key) { return HashString(*static_cast(key)); } bool nsCacheEntryHashTable::MatchEntry(const PLDHashEntryHdr* hashEntry, const void* key) { NS_ASSERTION(key != nullptr, "### nsCacheEntryHashTable::MatchEntry : null key"); nsCacheEntry* cacheEntry = ((nsCacheEntryHashTableEntry*)hashEntry)->cacheEntry; return cacheEntry->mKey.Equals(*(nsCString*)key); } void nsCacheEntryHashTable::MoveEntry(PLDHashTable* /* table */, const PLDHashEntryHdr* from, PLDHashEntryHdr* to) { new (KnownNotNull, to) nsCacheEntryHashTableEntry( std::move(*((nsCacheEntryHashTableEntry*)from))); // No need to destroy `from`. } void nsCacheEntryHashTable::ClearEntry(PLDHashTable* /* table */, PLDHashEntryHdr* hashEntry) { ((nsCacheEntryHashTableEntry*)hashEntry)->cacheEntry = nullptr; }