diff --git a/layout/build/nsLayoutStatics.cpp b/layout/build/nsLayoutStatics.cpp index e993d95cfebf..aeefa16b2f0d 100644 --- a/layout/build/nsLayoutStatics.cpp +++ b/layout/build/nsLayoutStatics.cpp @@ -30,6 +30,7 @@ #include "nsGkAtoms.h" #include "nsImageFrame.h" #include "nsLayoutStylesheetCache.h" +#include "mozilla/RuleProcessorCache.h" #include "nsPrincipal.h" #include "nsRange.h" #include "nsRegion.h" @@ -376,6 +377,7 @@ nsLayoutStatics::Shutdown() nsAttrValue::Shutdown(); nsContentUtils::Shutdown(); nsLayoutStylesheetCache::Shutdown(); + RuleProcessorCache::Shutdown(); ShutdownJSEnvironment(); nsGlobalWindow::ShutDown(); diff --git a/layout/style/CSSStyleSheet.cpp b/layout/style/CSSStyleSheet.cpp index aae529b9aeb4..0cab86918f51 100644 --- a/layout/style/CSSStyleSheet.cpp +++ b/layout/style/CSSStyleSheet.cpp @@ -46,6 +46,7 @@ #include "mozilla/dom/CSSStyleSheetBinding.h" #include "nsComponentManagerUtils.h" #include "nsNullPrincipal.h" +#include "mozilla/RuleProcessorCache.h" using namespace mozilla; using namespace mozilla::dom; @@ -1075,6 +1076,7 @@ CSSStyleSheet::CSSStyleSheet(CORSMode aCORSMode, ReferrerPolicy aReferrerPolicy) mOwningNode(nullptr), mDisabled(false), mDirty(false), + mInRuleProcessorCache(false), mScopeElement(nullptr), mRuleProcessors(nullptr) { @@ -1093,6 +1095,7 @@ CSSStyleSheet::CSSStyleSheet(const CSSStyleSheet& aCopy, mOwningNode(aOwningNodeToUse), mDisabled(aCopy.mDisabled), mDirty(aCopy.mDirty), + mInRuleProcessorCache(false), mScopeElement(nullptr), mInner(aCopy.mInner), mRuleProcessors(nullptr) @@ -1135,6 +1138,9 @@ CSSStyleSheet::~CSSStyleSheet() NS_ASSERTION(mRuleProcessors->Length() == 0, "destructing sheet with rule processor reference"); delete mRuleProcessors; // weak refs, should be empty here anyway } + if (mInRuleProcessorCache) { + RuleProcessorCache::RemoveSheet(this); + } } void @@ -1703,10 +1709,18 @@ CSSStyleSheet::List(FILE* out, int32_t aIndent) const void CSSStyleSheet::ClearRuleCascades() { + bool removedSheetFromRuleProcessorCache = false; if (mRuleProcessors) { nsCSSRuleProcessor **iter = mRuleProcessors->Elements(), **end = iter + mRuleProcessors->Length(); for(; iter != end; ++iter) { + if (!removedSheetFromRuleProcessorCache && (*iter)->IsShared()) { + // Since the sheet has been modified, we need to remove all + // RuleProcessorCache entries that contain this sheet, as the + // list of @-moz-document rules might have changed. + RuleProcessorCache::RemoveSheet(this); + removedSheetFromRuleProcessorCache = true; + } (*iter)->ClearRuleCascades(); } } diff --git a/layout/style/CSSStyleSheet.h b/layout/style/CSSStyleSheet.h index adeb25eff226..243cce05c7fc 100644 --- a/layout/style/CSSStyleSheet.h +++ b/layout/style/CSSStyleSheet.h @@ -239,6 +239,8 @@ public: nsresult ParseSheet(const nsAString& aInput); + void SetInRuleProcessorCache() { mInRuleProcessorCache = true; } + // nsIDOMStyleSheet interface NS_DECL_NSIDOMSTYLESHEET @@ -357,6 +359,7 @@ protected: nsINode* mOwningNode; // weak ref bool mDisabled; bool mDirty; // has been modified + bool mInRuleProcessorCache; nsRefPtr mScopeElement; CSSStyleSheetInner* mInner; diff --git a/layout/style/RuleProcessorCache.cpp b/layout/style/RuleProcessorCache.cpp new file mode 100644 index 000000000000..576b974d8290 --- /dev/null +++ b/layout/style/RuleProcessorCache.cpp @@ -0,0 +1,284 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +/* + * cache of re-usable nsCSSRuleProcessors for given sets of style sheets + */ + +#include "RuleProcessorCache.h" + +#include +#include "nsCSSRuleProcessor.h" +#include "nsThreadUtils.h" + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(RuleProcessorCache, nsIMemoryReporter) + +MOZ_DEFINE_MALLOC_SIZE_OF(RuleProcessorCacheMallocSizeOf) + +NS_IMETHODIMP +RuleProcessorCache::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) +{ + return MOZ_COLLECT_REPORT( + "explicit/layout/rule-processor-cache", KIND_HEAP, UNITS_BYTES, + SizeOfIncludingThis(RuleProcessorCacheMallocSizeOf), + "Memory used for cached rule processors."); +} + +RuleProcessorCache::~RuleProcessorCache() +{ + UnregisterWeakMemoryReporter(this); + + for (Entry& e : mEntries) { + for (DocumentEntry& de : e.mDocumentEntries) { + if (de.mRuleProcessor->GetExpirationState()->IsTracked()) { + mExpirationTracker.RemoveObject(de.mRuleProcessor); + } + de.mRuleProcessor->SetInRuleProcessorCache(false); + } + } +} + +void +RuleProcessorCache::InitMemoryReporter() +{ + RegisterWeakMemoryReporter(this); +} + +/* static */ bool +RuleProcessorCache::EnsureGlobal() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (gShutdown) { + return false; + } + + if (!gRuleProcessorCache) { + gRuleProcessorCache = new RuleProcessorCache; + gRuleProcessorCache->InitMemoryReporter(); + } + return true; +} + +/* static */ void +RuleProcessorCache::RemoveSheet(CSSStyleSheet* aSheet) +{ + if (!EnsureGlobal()) { + return; + } + gRuleProcessorCache->DoRemoveSheet(aSheet); +} + +#ifdef DEBUG +/* static */ bool +RuleProcessorCache::HasRuleProcessor(nsCSSRuleProcessor* aRuleProcessor) +{ + if (!EnsureGlobal()) { + return false; + } + return gRuleProcessorCache->DoHasRuleProcessor(aRuleProcessor); +} +#endif + +/* static */ void +RuleProcessorCache::RemoveRuleProcessor(nsCSSRuleProcessor* aRuleProcessor) +{ + if (!EnsureGlobal()) { + return; + } + gRuleProcessorCache->DoRemoveRuleProcessor(aRuleProcessor); +} + +/* static */ nsCSSRuleProcessor* +RuleProcessorCache::GetRuleProcessor(const nsTArray& aSheets, + nsPresContext* aPresContext) +{ + if (!EnsureGlobal()) { + return nullptr; + } + return gRuleProcessorCache->DoGetRuleProcessor(aSheets, aPresContext); +} + +/* static */ void +RuleProcessorCache::PutRuleProcessor( + const nsTArray& aSheets, + nsTArray&& aDocumentRulesInSheets, + const nsDocumentRuleResultCacheKey& aCacheKey, + nsCSSRuleProcessor* aRuleProcessor) +{ + if (!EnsureGlobal()) { + return; + } + gRuleProcessorCache->DoPutRuleProcessor(aSheets, Move(aDocumentRulesInSheets), + aCacheKey, aRuleProcessor); +} + +/* static */ void +RuleProcessorCache::StartTracking(nsCSSRuleProcessor* aRuleProcessor) +{ + if (!EnsureGlobal()) { + return; + } + return gRuleProcessorCache->DoStartTracking(aRuleProcessor); +} + +/* static */ void +RuleProcessorCache::StopTracking(nsCSSRuleProcessor* aRuleProcessor) +{ + if (!EnsureGlobal()) { + return; + } + return gRuleProcessorCache->DoStopTracking(aRuleProcessor); +} + +void +RuleProcessorCache::DoRemoveSheet(CSSStyleSheet* aSheet) +{ + Entry* last = std::remove_if(mEntries.begin(), mEntries.end(), + HasSheet_ThenRemoveRuleProcessors(this, aSheet)); + mEntries.TruncateLength(last - mEntries.begin()); +} + +nsCSSRuleProcessor* +RuleProcessorCache::DoGetRuleProcessor(const nsTArray& aSheets, + nsPresContext* aPresContext) +{ + for (Entry& e : mEntries) { + if (e.mSheets == aSheets) { + for (DocumentEntry& de : e.mDocumentEntries) { + if (de.mCacheKey.Matches(aPresContext, e.mDocumentRulesInSheets)) { + return de.mRuleProcessor; + } + } + // Entry::mSheets is unique; if we matched aSheets but didn't + // find a matching DocumentEntry, we won't find one later in + // mEntries. + return nullptr; + } + } + return nullptr; +} + +void +RuleProcessorCache::DoPutRuleProcessor( + const nsTArray& aSheets, + nsTArray&& aDocumentRulesInSheets, + const nsDocumentRuleResultCacheKey& aCacheKey, + nsCSSRuleProcessor* aRuleProcessor) +{ + MOZ_ASSERT(!aRuleProcessor->IsInRuleProcessorCache()); + + Entry* entry = nullptr; + for (Entry& e : mEntries) { + if (e.mSheets == aSheets) { + entry = &e; + break; + } + } + + if (!entry) { + entry = mEntries.AppendElement(); + entry->mSheets = aSheets; + entry->mDocumentRulesInSheets = aDocumentRulesInSheets; + for (CSSStyleSheet* sheet : aSheets) { + sheet->SetInRuleProcessorCache(); + } + } else { + MOZ_ASSERT(entry->mDocumentRulesInSheets == aDocumentRulesInSheets, + "DocumentRule array shouldn't have changed"); + } + +#ifdef DEBUG + for (DocumentEntry& de : entry->mDocumentEntries) { + MOZ_ASSERT(de.mCacheKey != aCacheKey, + "should not have duplicate document cache keys"); + } +#endif + + DocumentEntry* documentEntry = entry->mDocumentEntries.AppendElement(); + documentEntry->mCacheKey = aCacheKey; + documentEntry->mRuleProcessor = aRuleProcessor; + aRuleProcessor->SetInRuleProcessorCache(true); +} + +#ifdef DEBUG +bool +RuleProcessorCache::DoHasRuleProcessor(nsCSSRuleProcessor* aRuleProcessor) +{ + for (Entry& e : mEntries) { + for (DocumentEntry& de : e.mDocumentEntries) { + if (de.mRuleProcessor == aRuleProcessor) { + return true; + } + } + } + return false; +} +#endif + +void +RuleProcessorCache::DoRemoveRuleProcessor(nsCSSRuleProcessor* aRuleProcessor) +{ + MOZ_ASSERT(aRuleProcessor->IsInRuleProcessorCache()); + + aRuleProcessor->SetInRuleProcessorCache(false); + mExpirationTracker.RemoveObjectIfTracked(aRuleProcessor); + for (Entry& e : mEntries) { + for (size_t i = 0; i < e.mDocumentEntries.Length(); i++) { + if (e.mDocumentEntries[i].mRuleProcessor == aRuleProcessor) { + e.mDocumentEntries.RemoveElementAt(i); + return; + } + } + } + + MOZ_ASSERT_UNREACHABLE("should have found rule processor"); +} + +void +RuleProcessorCache::DoStartTracking(nsCSSRuleProcessor* aRuleProcessor) +{ + mExpirationTracker.AddObject(aRuleProcessor); +} + +void +RuleProcessorCache::DoStopTracking(nsCSSRuleProcessor* aRuleProcessor) +{ + mExpirationTracker.RemoveObjectIfTracked(aRuleProcessor); +} + +size_t +RuleProcessorCache::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) +{ + size_t n = aMallocSizeOf(this); + + int count = 0; + n += mEntries.SizeOfExcludingThis(aMallocSizeOf); + for (Entry& e : mEntries) { + n += e.mDocumentEntries.SizeOfExcludingThis(aMallocSizeOf); + for (DocumentEntry& de : e.mDocumentEntries) { + count++; + n += de.mRuleProcessor->SizeOfIncludingThis(aMallocSizeOf); + } + } + + return n; +} + +void +RuleProcessorCache::ExpirationTracker::RemoveObjectIfTracked( + nsCSSRuleProcessor* aRuleProcessor) +{ + if (aRuleProcessor->GetExpirationState()->IsTracked()) { + RemoveObject(aRuleProcessor); + } +} + +bool RuleProcessorCache::gShutdown = false; +mozilla::StaticRefPtr RuleProcessorCache::gRuleProcessorCache; diff --git a/layout/style/RuleProcessorCache.h b/layout/style/RuleProcessorCache.h new file mode 100644 index 000000000000..16090e0136d8 --- /dev/null +++ b/layout/style/RuleProcessorCache.h @@ -0,0 +1,149 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +/* + * cache of re-usable nsCSSRuleProcessors for given sets of style sheets + */ + +#ifndef mozilla_RuleProcessorCache_h +#define mozilla_RuleProcessorCache_h + +#include "mozilla/MemoryReporting.h" +#include "mozilla/StaticPtr.h" +#include "nsCSSRuleProcessor.h" +#include "nsExpirationTracker.h" +#include "nsIMediaList.h" +#include "nsIMemoryReporter.h" +#include "nsTArray.h" + +class nsCSSRuleProcessor; +namespace mozilla { +class CSSStyleSheet; +namespace css { +class DocumentRule; +} +} + +namespace mozilla { + +/** + * The RuleProcessorCache is a singleton object that caches + * nsCSSRuleProcessors keyed off a list of style sheets and the result of + * evaluating all @-moz-documents in the style sheets. nsStyleSet gets and + * puts nsCSSRuleProcessors from/to the RuleProcessorCache. + * + * State bits on CSSStyleSheet and nsCSSRuleProcessor track whether they are in + * the RuleProcessorCache. This lets us remove them from the RuleProcessorCache + * when they're going away. + */ +class RuleProcessorCache final : public nsIMemoryReporter +{ + NS_DECL_ISUPPORTS + NS_DECL_NSIMEMORYREPORTER + +public: + static nsCSSRuleProcessor* GetRuleProcessor( + const nsTArray& aSheets, + nsPresContext* aPresContext); + static void PutRuleProcessor( + const nsTArray& aSheets, + nsTArray&& aDocumentRulesInSheets, + const nsDocumentRuleResultCacheKey& aCacheKey, + nsCSSRuleProcessor* aRuleProcessor); + static void StartTracking(nsCSSRuleProcessor* aRuleProcessor); + static void StopTracking(nsCSSRuleProcessor* aRuleProcessor); + +#ifdef DEBUG + static bool HasRuleProcessor(nsCSSRuleProcessor* aRuleProcessor); +#endif + static void RemoveRuleProcessor(nsCSSRuleProcessor* aRuleProcessor); + static void RemoveSheet(CSSStyleSheet* aSheet); + + static void Shutdown() { gShutdown = true; gRuleProcessorCache = nullptr; } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); + +private: + class ExpirationTracker : public nsExpirationTracker + { + public: + ExpirationTracker(RuleProcessorCache* aCache) + : nsExpirationTracker(10000) + , mCache(aCache) {} + + void RemoveObjectIfTracked(nsCSSRuleProcessor* aRuleProcessor); + + virtual void NotifyExpired(nsCSSRuleProcessor* aRuleProcessor) override { + mCache->RemoveRuleProcessor(aRuleProcessor); + } + + private: + RuleProcessorCache* mCache; + }; + + RuleProcessorCache() : mExpirationTracker(this) {} + ~RuleProcessorCache(); + + void InitMemoryReporter(); + + static bool EnsureGlobal(); + static StaticRefPtr gRuleProcessorCache; + static bool gShutdown; + + void DoRemoveSheet(CSSStyleSheet* aSheet); + nsCSSRuleProcessor* DoGetRuleProcessor( + const nsTArray& aSheets, + nsPresContext* aPresContext); + void DoPutRuleProcessor(const nsTArray& aSheets, + nsTArray&& aDocumentRulesInSheets, + const nsDocumentRuleResultCacheKey& aCacheKey, + nsCSSRuleProcessor* aRuleProcessor); +#ifdef DEBUG + bool DoHasRuleProcessor(nsCSSRuleProcessor* aRuleProcessor); +#endif + void DoRemoveRuleProcessor(nsCSSRuleProcessor* aRuleProcessor); + void DoStartTracking(nsCSSRuleProcessor* aRuleProcessor); + void DoStopTracking(nsCSSRuleProcessor* aRuleProcessor); + + struct DocumentEntry { + nsDocumentRuleResultCacheKey mCacheKey; + nsRefPtr mRuleProcessor; + }; + + struct Entry { + nsTArray mSheets; + nsTArray mDocumentRulesInSheets; + nsTArray mDocumentEntries; + }; + + // Function object to test whether an Entry object has a given sheet + // in its mSheets array. If it does, removes all of its rule processors + // before returning true. + struct HasSheet_ThenRemoveRuleProcessors { + HasSheet_ThenRemoveRuleProcessors(RuleProcessorCache* aCache, + CSSStyleSheet* aSheet) + : mCache(aCache), mSheet(aSheet) {} + bool operator()(Entry& aEntry) { + if (aEntry.mSheets.Contains(mSheet)) { + for (DocumentEntry& de : aEntry.mDocumentEntries) { + de.mRuleProcessor->SetInRuleProcessorCache(false); + mCache->mExpirationTracker.RemoveObjectIfTracked(de.mRuleProcessor); + } + return true; + } + return false; + } + RuleProcessorCache* mCache; + CSSStyleSheet* mSheet; + }; + + ExpirationTracker mExpirationTracker; + nsTArray mEntries; +}; + +} // namespace mozilla + +#endif // mozilla_RuleProcessorCache_h diff --git a/layout/style/moz.build b/layout/style/moz.build index 4cb634d3d617..ba03113f9c87 100644 --- a/layout/style/moz.build +++ b/layout/style/moz.build @@ -86,6 +86,7 @@ EXPORTS.mozilla += [ 'CSSVariableValues.h', 'IncrementalClearCOMRuleArray.h', 'RuleNodeCacheConditions.h', + 'RuleProcessorCache.h', 'StyleAnimationValue.h', ] @@ -165,6 +166,7 @@ UNIFIED_SOURCES += [ 'nsStyleUtil.cpp', 'nsTransitionManager.cpp', 'RuleNodeCacheConditions.cpp', + 'RuleProcessorCache.cpp', 'StyleAnimationValue.cpp', 'StyleRule.cpp', 'SVGAttrAnimationRuleProcessor.cpp', diff --git a/layout/style/nsCSSRuleProcessor.cpp b/layout/style/nsCSSRuleProcessor.cpp index 0057638f59d7..303608c12c53 100644 --- a/layout/style/nsCSSRuleProcessor.cpp +++ b/layout/style/nsCSSRuleProcessor.cpp @@ -54,6 +54,7 @@ #include "mozilla/LookAndFeel.h" #include "mozilla/Likely.h" #include "mozilla/TypedEnumBits.h" +#include "RuleProcessorCache.h" using namespace mozilla; using namespace mozilla::dom; @@ -1005,7 +1006,8 @@ nsCSSRuleProcessor::nsCSSRuleProcessor(const sheet_array_type& aSheets, uint8_t aSheetType, Element* aScopeElement, nsCSSRuleProcessor* - aPreviousCSSRuleProcessor) + aPreviousCSSRuleProcessor, + bool aIsShared) : mSheets(aSheets) , mRuleCascades(nullptr) , mPreviousCacheKey(aPreviousCSSRuleProcessor @@ -1014,6 +1016,8 @@ nsCSSRuleProcessor::nsCSSRuleProcessor(const sheet_array_type& aSheets, , mLastPresContext(nullptr) , mScopeElement(aScopeElement) , mSheetType(aSheetType) + , mIsShared(aIsShared) + , mInRuleProcessorCache(false) { NS_ASSERTION(!!mScopeElement == (aSheetType == nsStyleSet::eScopedDocSheet), "aScopeElement must be specified iff aSheetType is " @@ -1025,6 +1029,10 @@ nsCSSRuleProcessor::nsCSSRuleProcessor(const sheet_array_type& aSheets, nsCSSRuleProcessor::~nsCSSRuleProcessor() { + if (mInRuleProcessorCache) { + RuleProcessorCache::RemoveRuleProcessor(this); + } + MOZ_ASSERT(!mExpirationState.IsTracked()); ClearSheets(); ClearRuleCascades(); } @@ -2990,6 +2998,13 @@ nsCSSRuleProcessor::ClearRuleCascades() mPreviousCacheKey = CloneMQCacheKey(); } + // No need to remove the rule processor from the RuleProcessorCache here, + // since CSSStyleSheet::ClearRuleCascades will have called + // RuleProcessorCache::RemoveSheet() passing itself, which will catch + // this rule processor (and any others for different @-moz-document + // cache key results). + MOZ_ASSERT(!RuleProcessorCache::HasRuleProcessor(this)); + // We rely on our caller (perhaps indirectly) to do something that // will rebuild style data and the user font set (either // nsIPresShell::ReconstructStyleData or diff --git a/layout/style/nsCSSRuleProcessor.h b/layout/style/nsCSSRuleProcessor.h index b91096f20c9a..17edf0a88464 100644 --- a/layout/style/nsCSSRuleProcessor.h +++ b/layout/style/nsCSSRuleProcessor.h @@ -18,6 +18,7 @@ #include "nsIStyleRuleProcessor.h" #include "nsTArray.h" #include "nsAutoPtr.h" +#include "nsExpirationTracker.h" #include "nsRuleWalker.h" #include "mozilla/UniquePtr.h" @@ -59,7 +60,8 @@ public: nsCSSRuleProcessor(const sheet_array_type& aSheets, uint8_t aSheetType, mozilla::dom::Element* aScopeElement, - nsCSSRuleProcessor* aPreviousCSSRuleProcessor); + nsCSSRuleProcessor* aPreviousCSSRuleProcessor, + bool aIsShared = false); NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS(nsCSSRuleProcessor) @@ -161,6 +163,16 @@ public: */ mozilla::dom::Element* GetScopeElement() const { return mScopeElement; } + bool IsShared() const { return mIsShared; } + + nsExpirationState* GetExpirationState() { return &mExpirationState; } + void SetInRuleProcessorCache(bool aVal) { + MOZ_ASSERT(mIsShared); + printf("%p SetInRuleProcessorCache %d\n", this, aVal); + mInRuleProcessorCache = aVal; + } + bool IsInRuleProcessorCache() const { return mInRuleProcessorCache; } + #ifdef XP_WIN // Cached theme identifier for the moz-windows-theme media query. static uint8_t GetWindowsThemeIdentifier(); @@ -214,9 +226,14 @@ private: // Only used if mSheetType == nsStyleSet::eScopedDocSheet. nsRefPtr mScopeElement; + nsExpirationState mExpirationState; + // type of stylesheet using this processor uint8_t mSheetType; // == nsStyleSet::sheetType + const bool mIsShared; + bool mInRuleProcessorCache; + #ifdef XP_WIN static uint8_t sWinThemeId; #endif