mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-07 18:04:46 +00:00
Bug 1599160 - Better integration of the shared stylesheet cache with the network cache. r=tnikkel,mayhemer,heycam
Make the stylesheet cache respect the same headers as the image cache does. This makes no-cache stylesheets work as they do now, which is useful for developers that want to develop sites locally, and for shift-reloads, etc. Differential Revision: https://phabricator.services.mozilla.com/D78659
This commit is contained in:
parent
d999791a43
commit
5048e0ec84
@ -153,6 +153,7 @@
|
||||
#include "nsHTMLTags.h"
|
||||
#include "nsIAnonymousContentCreator.h"
|
||||
#include "nsIAsyncVerifyRedirectCallback.h"
|
||||
#include "nsICacheInfoChannel.h"
|
||||
#include "nsICategoryManager.h"
|
||||
#include "nsIChannelEventSink.h"
|
||||
#include "nsIConsoleService.h"
|
||||
@ -10357,3 +10358,37 @@ ScreenIntMargin nsContentUtils::GetWindowSafeAreaInsets(
|
||||
|
||||
return windowSafeAreaInsets;
|
||||
}
|
||||
|
||||
/* static */
|
||||
nsContentUtils::SubresourceCacheValidationInfo
|
||||
nsContentUtils::GetSubresourceCacheValidationInfo(nsIRequest* aRequest) {
|
||||
SubresourceCacheValidationInfo info;
|
||||
if (nsCOMPtr<nsICacheInfoChannel> cache = do_QueryInterface(aRequest)) {
|
||||
uint32_t value = 0;
|
||||
if (NS_SUCCEEDED(cache->GetCacheTokenExpirationTime(&value))) {
|
||||
info.mExpirationTime.emplace(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine whether the cache entry must be revalidated when we try to use
|
||||
// it. Currently, only HTTP specifies this information...
|
||||
if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest)) {
|
||||
Unused << httpChannel->IsNoStoreResponse(&info.mMustRevalidate);
|
||||
|
||||
if (!info.mMustRevalidate) {
|
||||
Unused << httpChannel->IsNoCacheResponse(&info.mMustRevalidate);
|
||||
}
|
||||
|
||||
// FIXME(bug 1644173): Why this check?
|
||||
if (!info.mMustRevalidate) {
|
||||
nsAutoCString cacheHeader;
|
||||
Unused << httpChannel->GetResponseHeader(
|
||||
NS_LITERAL_CSTRING("Cache-Control"), cacheHeader);
|
||||
if (PL_strcasestr(cacheHeader.get(), "must-revalidate")) {
|
||||
info.mMustRevalidate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
@ -48,6 +48,7 @@
|
||||
#include "mozilla/dom/Document.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
#include "nsRFPService.h"
|
||||
#include "prtime.h"
|
||||
|
||||
#if defined(XP_WIN)
|
||||
// Undefine LoadImage to prevent naming conflict with Windows.
|
||||
@ -3246,6 +3247,23 @@ class nsContentUtils {
|
||||
nsIScreen* aScreen, const mozilla::ScreenIntMargin& aSafeareaInsets,
|
||||
const mozilla::LayoutDeviceIntRect& aWindowRect);
|
||||
|
||||
struct SubresourceCacheValidationInfo {
|
||||
// The expiration time, in seconds, if known.
|
||||
Maybe<uint32_t> mExpirationTime;
|
||||
bool mMustRevalidate = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets cache validation info for subresources such as images or CSS
|
||||
* stylesheets.
|
||||
*/
|
||||
static SubresourceCacheValidationInfo GetSubresourceCacheValidationInfo(
|
||||
nsIRequest*);
|
||||
|
||||
static uint32_t SecondsFromPRTime(PRTime aTime) {
|
||||
return uint32_t(int64_t(aTime) / int64_t(PR_USEC_PER_SEC));
|
||||
}
|
||||
|
||||
private:
|
||||
static bool InitializeEventTable();
|
||||
|
||||
|
@ -960,11 +960,11 @@ static nsresult NewImageChannel(
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* static */
|
||||
uint32_t imgCacheEntry::SecondsFromPRTime(PRTime prTime) {
|
||||
return uint32_t(int64_t(prTime) / int64_t(PR_USEC_PER_SEC));
|
||||
static uint32_t SecondsFromPRTime(PRTime aTime) {
|
||||
return nsContentUtils::SecondsFromPRTime(aTime);
|
||||
}
|
||||
|
||||
/* static */
|
||||
imgCacheEntry::imgCacheEntry(imgLoader* loader, imgRequest* request,
|
||||
bool forcePrincipalCheck)
|
||||
: mLoader(loader),
|
||||
@ -1822,8 +1822,8 @@ bool imgLoader::ValidateEntry(
|
||||
// If the expiration time is zero, then the request has not gotten far enough
|
||||
// to know when it will expire.
|
||||
uint32_t expiryTime = aEntry->GetExpiryTime();
|
||||
bool hasExpired = expiryTime != 0 &&
|
||||
expiryTime <= imgCacheEntry::SecondsFromPRTime(PR_Now());
|
||||
bool hasExpired =
|
||||
expiryTime != 0 && expiryTime <= SecondsFromPRTime(PR_Now());
|
||||
|
||||
nsresult rv;
|
||||
|
||||
@ -1840,8 +1840,7 @@ bool imgLoader::ValidateEntry(
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
// nsIFile uses millisec, NSPR usec
|
||||
fileLastMod *= 1000;
|
||||
hasExpired =
|
||||
imgCacheEntry::SecondsFromPRTime((PRTime)fileLastMod) > lastModTime;
|
||||
hasExpired = SecondsFromPRTime((PRTime)fileLastMod) > lastModTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,8 +39,6 @@ namespace image {} // namespace image
|
||||
|
||||
class imgCacheEntry {
|
||||
public:
|
||||
static uint32_t SecondsFromPRTime(PRTime prTime);
|
||||
|
||||
imgCacheEntry(imgLoader* loader, imgRequest* request,
|
||||
bool aForcePrincipalCheck);
|
||||
~imgCacheEntry();
|
||||
|
@ -517,53 +517,26 @@ void imgRequest::UpdateCacheEntrySize() {
|
||||
void imgRequest::SetCacheValidation(imgCacheEntry* aCacheEntry,
|
||||
nsIRequest* aRequest) {
|
||||
/* get the expires info */
|
||||
if (aCacheEntry) {
|
||||
// Expiration time defaults to 0. We set the expiration time on our
|
||||
// entry if it hasn't been set yet.
|
||||
if (aCacheEntry->GetExpiryTime() == 0) {
|
||||
uint32_t expiration = 0;
|
||||
nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(aRequest));
|
||||
if (cacheChannel) {
|
||||
/* get the expiration time from the caching channel's token */
|
||||
cacheChannel->GetCacheTokenExpirationTime(&expiration);
|
||||
}
|
||||
if (expiration == 0) {
|
||||
// If the channel doesn't support caching, then ensure this expires the
|
||||
// next time it is used.
|
||||
expiration = imgCacheEntry::SecondsFromPRTime(PR_Now()) - 1;
|
||||
}
|
||||
aCacheEntry->SetExpiryTime(expiration);
|
||||
}
|
||||
if (!aCacheEntry || aCacheEntry->GetExpiryTime() != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine whether the cache entry must be revalidated when we try to use
|
||||
// it. Currently, only HTTP specifies this information...
|
||||
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
|
||||
if (httpChannel) {
|
||||
bool bMustRevalidate = false;
|
||||
auto info = nsContentUtils::GetSubresourceCacheValidationInfo(aRequest);
|
||||
|
||||
Unused << httpChannel->IsNoStoreResponse(&bMustRevalidate);
|
||||
|
||||
if (!bMustRevalidate) {
|
||||
Unused << httpChannel->IsNoCacheResponse(&bMustRevalidate);
|
||||
}
|
||||
|
||||
if (!bMustRevalidate) {
|
||||
nsAutoCString cacheHeader;
|
||||
|
||||
Unused << httpChannel->GetResponseHeader(
|
||||
NS_LITERAL_CSTRING("Cache-Control"), cacheHeader);
|
||||
if (PL_strcasestr(cacheHeader.get(), "must-revalidate")) {
|
||||
bMustRevalidate = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache entries default to not needing to validate. We ensure that
|
||||
// multiple calls to this function don't override an earlier decision to
|
||||
// validate by making validation a one-way decision.
|
||||
if (bMustRevalidate) {
|
||||
aCacheEntry->SetMustValidate(bMustRevalidate);
|
||||
}
|
||||
}
|
||||
// Expiration time defaults to 0. We set the expiration time on our entry if
|
||||
// it hasn't been set yet.
|
||||
if (!info.mExpirationTime) {
|
||||
// If the channel doesn't support caching, then ensure this expires the
|
||||
// next time it is used.
|
||||
info.mExpirationTime.emplace(nsContentUtils::SecondsFromPRTime(PR_Now()) -
|
||||
1);
|
||||
}
|
||||
aCacheEntry->SetExpiryTime(*info.mExpirationTime);
|
||||
// Cache entries default to not needing to validate. We ensure that
|
||||
// multiple calls to this function don't override an earlier decision to
|
||||
// validate by making validation a one-way decision.
|
||||
if (info.mMustRevalidate) {
|
||||
aCacheEntry->SetMustValidate(info.mMustRevalidate);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "mozilla/URLPreloader.h"
|
||||
#include "nsIRunnable.h"
|
||||
#include "nsITimedChannel.h"
|
||||
#include "nsICachingChannel.h"
|
||||
#include "nsSyncLoadService.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsString.h"
|
||||
@ -900,7 +901,7 @@ std::tuple<RefPtr<StyleSheet>, Loader::SheetState> Loader::CreateSheet(
|
||||
GetFallbackEncoding(*this, aLinkingContent,
|
||||
aPreloadOrParentDataEncoding),
|
||||
aCORSMode, aParsingMode, mCompatMode, sriMetadata, aIsPreload);
|
||||
auto cacheResult = mSheets->Lookup(key, aSyncLoad);
|
||||
auto cacheResult = mSheets->Lookup(*this, key, aSyncLoad);
|
||||
if (const auto& [styleSheet, sheetState] = cacheResult; styleSheet) {
|
||||
LOG((" Hit cache with state: %s", gStateStrings[size_t(sheetState)]));
|
||||
return cacheResult;
|
||||
@ -1410,6 +1411,9 @@ Loader::Completed Loader::ParseSheet(const nsACString& aBytes,
|
||||
SheetLoadData& aLoadData,
|
||||
AllowAsyncParse aAllowAsync) {
|
||||
LOG(("css::Loader::ParseSheet"));
|
||||
if (aLoadData.mURI) {
|
||||
LOG_URI(" Load succeeded for URI: '%s', parsing", aLoadData.mURI);
|
||||
}
|
||||
AUTO_PROFILER_LABEL("css::Loader::ParseSheet", LAYOUT_CSSParsing);
|
||||
|
||||
++mParsedSheetCount;
|
||||
@ -2143,6 +2147,22 @@ nsIPrincipal* Loader::LoaderPrincipal() const {
|
||||
return nsContentUtils::GetSystemPrincipal();
|
||||
}
|
||||
|
||||
bool Loader::ShouldBypassCache() const {
|
||||
if (!mDocument) {
|
||||
return false;
|
||||
}
|
||||
RefPtr<nsILoadGroup> lg = mDocument->GetDocumentLoadGroup();
|
||||
if (!lg) {
|
||||
return false;
|
||||
}
|
||||
nsLoadFlags flags;
|
||||
if (NS_FAILED(lg->GetLoadFlags(&flags))) {
|
||||
return false;
|
||||
}
|
||||
return flags & (nsIRequest::LOAD_BYPASS_CACHE |
|
||||
nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE);
|
||||
}
|
||||
|
||||
void Loader::BlockOnload() {
|
||||
if (mDocument) {
|
||||
mDocument->BlockOnload();
|
||||
|
@ -505,6 +505,8 @@ class Loader final {
|
||||
// owned by a document, or the system principal otherwise.
|
||||
nsIPrincipal* LoaderPrincipal() const;
|
||||
|
||||
bool ShouldBypassCache() const;
|
||||
|
||||
private:
|
||||
friend class mozilla::SharedStyleSheetCache;
|
||||
friend class SheetLoadData;
|
||||
|
@ -79,9 +79,16 @@ static void AssertIncompleteSheetMatches(const SheetLoadData& aData,
|
||||
"CSSOM shouldn't allow access to incomplete sheets");
|
||||
}
|
||||
|
||||
auto SharedStyleSheetCache::Lookup(SheetLoadDataHashKey& aKey, bool aSyncLoad)
|
||||
-> CacheResult {
|
||||
bool SharedStyleSheetCache::CompleteSheet::Expired() const {
|
||||
return mExpirationTime &&
|
||||
mExpirationTime <= nsContentUtils::SecondsFromPRTime(PR_Now());
|
||||
}
|
||||
|
||||
SharedStyleSheetCache::CacheResult SharedStyleSheetCache::Lookup(
|
||||
css::Loader& aLoader, const SheetLoadDataHashKey& aKey, bool aSyncLoad) {
|
||||
nsIURI* uri = aKey.URI();
|
||||
LOG(("SharedStyleSheetCache::Lookup(%s)", uri->GetSpecOrDefault().get()));
|
||||
|
||||
// Try to find first in the XUL prototype cache.
|
||||
if (dom::IsChromeURI(uri)) {
|
||||
nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
|
||||
@ -100,28 +107,35 @@ auto SharedStyleSheetCache::Lookup(SheetLoadDataHashKey& aKey, bool aSyncLoad)
|
||||
return {CloneSheet(*sheet), SheetState::Complete};
|
||||
}
|
||||
|
||||
LOG(
|
||||
(" Not cloning due to forced unique inner or mismatched "
|
||||
"parsing mode"));
|
||||
LOG((" Not cloning due to mismatched parsing mode"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now complete sheets.
|
||||
if (auto lookup = mCompleteSheets.Lookup(aKey)) {
|
||||
LOG((" From completed: %p", lookup.Data().get()));
|
||||
AssertComplete(*lookup.Data());
|
||||
MOZ_ASSERT(lookup.Data()->ParsingMode() == aKey.ParsingMode());
|
||||
const CompleteSheet& completeSheet = lookup.Data();
|
||||
// We can assert the stylesheet has not been modified, as we clone it on
|
||||
// insertion.
|
||||
StyleSheet* cachedSheet = lookup.Data();
|
||||
MOZ_ASSERT(!cachedSheet->HasForcedUniqueInner());
|
||||
MOZ_ASSERT(!cachedSheet->HasModifiedRules());
|
||||
StyleSheet& cachedSheet = *completeSheet.mSheet;
|
||||
LOG((" From completed: %p", &cachedSheet));
|
||||
|
||||
RefPtr<StyleSheet> clone = CloneSheet(*cachedSheet);
|
||||
MOZ_ASSERT(!clone->HasForcedUniqueInner());
|
||||
MOZ_ASSERT(!clone->HasModifiedRules());
|
||||
return {std::move(clone), SheetState::Complete};
|
||||
if ((!aLoader.ShouldBypassCache() && !completeSheet.Expired()) ||
|
||||
aLoader.mLoadsPerformed.Contains(aKey)) {
|
||||
LOG(
|
||||
(" Not expired yet, or previously loaded already in "
|
||||
"that document"));
|
||||
|
||||
AssertComplete(cachedSheet);
|
||||
MOZ_ASSERT(cachedSheet.ParsingMode() == aKey.ParsingMode());
|
||||
MOZ_ASSERT(!cachedSheet.HasForcedUniqueInner());
|
||||
MOZ_ASSERT(!cachedSheet.HasModifiedRules());
|
||||
|
||||
RefPtr<StyleSheet> clone = CloneSheet(cachedSheet);
|
||||
MOZ_ASSERT(!clone->HasForcedUniqueInner());
|
||||
MOZ_ASSERT(!clone->HasModifiedRules());
|
||||
return {std::move(clone), SheetState::Complete};
|
||||
}
|
||||
}
|
||||
|
||||
if (aSyncLoad) {
|
||||
@ -189,7 +203,7 @@ size_t SharedStyleSheetCache::SizeOfIncludingThis(
|
||||
|
||||
n += mCompleteSheets.ShallowSizeOfExcludingThis(aMallocSizeOf);
|
||||
for (auto iter = mCompleteSheets.ConstIter(); !iter.Done(); iter.Next()) {
|
||||
n += iter.UserData()->SizeOfIncludingThis(aMallocSizeOf);
|
||||
n += iter.UserData().mSheet->SizeOfIncludingThis(aMallocSizeOf);
|
||||
}
|
||||
|
||||
// Measurement of the following members may be added later if DMD finds it is
|
||||
@ -312,19 +326,30 @@ void SharedStyleSheetCache::InsertIntoCompleteCacheIfNeeded(
|
||||
SheetLoadData& aData) {
|
||||
MOZ_ASSERT(aData.mLoader->GetDocument(),
|
||||
"We only cache document-associated sheets");
|
||||
LOG(("SharedStyleSheetCache::InsertIntoCompleteCacheIfNeeded"));
|
||||
// If we ever start doing this for failed loads, we'll need to adjust the
|
||||
// PostLoadEvent code that thinks anything already complete must have loaded
|
||||
// succesfully.
|
||||
if (aData.mLoadFailed) {
|
||||
LOG((" Load failed, bailing"));
|
||||
return;
|
||||
}
|
||||
|
||||
// If this sheet came from the cache already, there's no need to override
|
||||
// anything.
|
||||
if (aData.mSheetAlreadyComplete) {
|
||||
LOG((" Sheet came from the cache, bailing"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!aData.mURI) {
|
||||
LOG((" Inline style sheet, bailing"));
|
||||
// Inline sheet caching happens in Loader::mInlineSheets.
|
||||
return;
|
||||
}
|
||||
|
||||
if (aData.mSheet->IsConstructed()) {
|
||||
LOG((" Constructable style sheet, bailing"));
|
||||
// Constructable sheets are not worth caching, they're always unique.
|
||||
return;
|
||||
}
|
||||
@ -352,18 +377,25 @@ void SharedStyleSheetCache::InsertIntoCompleteCacheIfNeeded(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG((" Putting style sheet in shared cache: %s",
|
||||
aData.mURI->GetSpecOrDefault().get()));
|
||||
SheetLoadDataHashKey key(aData);
|
||||
MOZ_ASSERT(sheet->IsComplete(), "Should only be caching complete sheets");
|
||||
|
||||
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
||||
for (const auto& entry : mCompleteSheets) {
|
||||
MOZ_DIAGNOSTIC_ASSERT(
|
||||
entry.GetData() != sheet || key.KeyEquals(entry.GetKey()),
|
||||
"Same sheet, different keys?");
|
||||
if (!key.KeyEquals(entry.GetKey())) {
|
||||
MOZ_DIAGNOSTIC_ASSERT(entry.GetData().mSheet != sheet,
|
||||
"Same sheet, different keys?");
|
||||
} else {
|
||||
MOZ_DIAGNOSTIC_ASSERT(
|
||||
entry.GetData().Expired() || aData.mLoader->ShouldBypassCache(),
|
||||
"Overriding existing complete entry?");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
mCompleteSheets.Put(key, std::move(sheet));
|
||||
mCompleteSheets.Put(key, {aData.mExpirationTime, std::move(sheet)});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,7 @@ class SharedStyleSheetCache final : public nsIMemoryReporter {
|
||||
|
||||
// A cache hit or miss. It is a miss if the `StyleSheet` is null.
|
||||
using CacheResult = std::tuple<RefPtr<StyleSheet>, css::Loader::SheetState>;
|
||||
CacheResult Lookup(SheetLoadDataHashKey&, bool aSyncLoad);
|
||||
CacheResult Lookup(css::Loader&, const SheetLoadDataHashKey&, bool aSyncLoad);
|
||||
|
||||
// Tries to coalesce with an already existing load. The sheet state must be
|
||||
// the one that Lookup returned, if it returned a sheet.
|
||||
@ -112,7 +112,14 @@ class SharedStyleSheetCache final : public nsIMemoryReporter {
|
||||
|
||||
~SharedStyleSheetCache();
|
||||
|
||||
nsRefPtrHashtable<SheetLoadDataHashKey, StyleSheet> mCompleteSheets;
|
||||
struct CompleteSheet {
|
||||
uint32_t mExpirationTime = 0;
|
||||
RefPtr<StyleSheet> mSheet;
|
||||
|
||||
bool Expired() const;
|
||||
};
|
||||
|
||||
nsDataHashtable<SheetLoadDataHashKey, CompleteSheet> mCompleteSheets;
|
||||
nsRefPtrHashtable<SheetLoadDataHashKey, css::SheetLoadData> mPendingDatas;
|
||||
// The SheetLoadData pointers in mLoadingDatas below are weak references.
|
||||
//
|
||||
|
@ -111,6 +111,10 @@ class SheetLoadData final : public nsIRunnable, public nsIThreadObserver {
|
||||
// during the parse
|
||||
const RefPtr<SheetLoadData> mParentData;
|
||||
|
||||
// The expiration time of the channel that has loaded this data, if
|
||||
// applicable.
|
||||
uint32_t mExpirationTime = 0;
|
||||
|
||||
// Number of sheets we @import-ed that are still loading
|
||||
uint32_t mPendingChildren;
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "mozilla/Encoding.h"
|
||||
#include "mozilla/ScopeExit.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsIChannel.h"
|
||||
#include "nsIInputStream.h"
|
||||
#include "nsISupportsPriority.h"
|
||||
@ -132,6 +133,15 @@ StreamLoader::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
|
||||
}
|
||||
} // run destructor for `bytes`
|
||||
|
||||
auto info = nsContentUtils::GetSubresourceCacheValidationInfo(aRequest);
|
||||
|
||||
// For now, we never cache entries that we have to revalidate.
|
||||
if (!info.mExpirationTime || info.mMustRevalidate) {
|
||||
info.mExpirationTime =
|
||||
Some(nsContentUtils::SecondsFromPRTime(PR_Now()) - 1);
|
||||
}
|
||||
mSheetLoadData->mExpirationTime = *info.mExpirationTime;
|
||||
|
||||
// For reasons I don't understand, factoring the below lines into
|
||||
// a method on SheetLoadData resulted in a linker error. Hence,
|
||||
// accessing fields of mSheetLoadData from here.
|
||||
|
Loading…
Reference in New Issue
Block a user