SharedStyleSheetCache.cpp (10135B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 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 #include "SharedStyleSheetCache.h" 8 9 #include "mozilla/MemoryReporting.h" 10 #include "mozilla/ServoBindings.h" 11 #include "mozilla/StoragePrincipalHelper.h" 12 #include "mozilla/StyleSheet.h" 13 #include "mozilla/css/SheetLoadData.h" 14 #include "mozilla/dom/ContentParent.h" 15 #include "mozilla/dom/Document.h" 16 #include "nsContentUtils.h" 17 #include "nsXULPrototypeCache.h" 18 19 extern mozilla::LazyLogModule sCssLoaderLog; 20 21 #define LOG(...) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) 22 23 namespace mozilla { 24 25 NS_IMPL_ISUPPORTS(SharedStyleSheetCache, nsIMemoryReporter) 26 27 MOZ_DEFINE_MALLOC_SIZE_OF(SharedStyleSheetCacheMallocSizeOf) 28 29 SharedStyleSheetCache::SharedStyleSheetCache() = default; 30 31 void SharedStyleSheetCache::Init() { 32 RegisterWeakMemoryReporter(this); 33 auto ClearCache = [](const char*, void*) { Clear(); }; 34 Preferences::RegisterPrefixCallback(ClearCache, "layout.css."); 35 } 36 37 SharedStyleSheetCache::~SharedStyleSheetCache() { 38 UnregisterWeakMemoryReporter(this); 39 } 40 41 void SharedStyleSheetCache::LoadCompleted(SharedStyleSheetCache* aCache, 42 StyleSheetLoadData& aData, 43 nsresult aStatus) { 44 // If aStatus is a failure we need to mark this data failed. We also need to 45 // mark any ancestors of a failing data as failed and any sibling of a 46 // failing data as failed. Note that SheetComplete is never called on a 47 // SheetLoadData that is the mNext of some other SheetLoadData. 48 nsresult cancelledStatus = aStatus; 49 if (NS_FAILED(aStatus)) { 50 css::Loader::MarkLoadTreeFailed(aData); 51 } else { 52 cancelledStatus = NS_BINDING_ABORTED; 53 css::SheetLoadData* data = &aData; 54 do { 55 if (data->IsCancelled()) { 56 // We only need to mark loads for this loader as cancelled, so as to not 57 // fire error events in unrelated documents. 58 css::Loader::MarkLoadTreeFailed(*data, data->mLoader); 59 } 60 } while ((data = data->mNext)); 61 } 62 63 // 8 is probably big enough for all our common cases. It's not likely that 64 // imports will nest more than 8 deep, and multiple sheets with the same URI 65 // are rare. 66 AutoTArray<RefPtr<css::SheetLoadData>, 8> datasToNotify; 67 LoadCompletedInternal(aCache, aData, datasToNotify); 68 69 // Now it's safe to go ahead and notify observers 70 for (RefPtr<css::SheetLoadData>& data : datasToNotify) { 71 auto status = data->IsCancelled() ? cancelledStatus : aStatus; 72 data->mLoader->NotifyObservers(*data, status); 73 } 74 } 75 76 void SharedStyleSheetCache::InsertIfNeeded(css::SheetLoadData& aData) { 77 MOZ_ASSERT(aData.mLoader->IsDocumentAssociated(), 78 "We only cache document-associated sheets"); 79 LOG("SharedStyleSheetCache::InsertIfNeeded"); 80 // If we ever start doing this for failed loads, we'll need to adjust the 81 // PostLoadEvent code that thinks anything already complete must have loaded 82 // succesfully. 83 if (aData.mLoadFailed) { 84 LOG(" Load failed, bailing"); 85 return; 86 } 87 88 // If this sheet came from the cache already, there's no need to override 89 // anything. 90 if (aData.mSheetAlreadyComplete) { 91 LOG(" Sheet came from the cache, bailing"); 92 return; 93 } 94 95 if (!aData.mURI) { 96 LOG(" Inline or constructable style sheet, bailing"); 97 // Inline sheet caching happens in Loader::mInlineSheets, where we still 98 // have the input text available. 99 // Constructable sheets are not worth caching, they're always unique. 100 return; 101 } 102 103 LOG(" Putting style sheet in shared cache: %s", 104 aData.mURI->GetSpecOrDefault().get()); 105 Insert(aData); 106 } 107 108 void SharedStyleSheetCache::LoadCompletedInternal( 109 SharedStyleSheetCache* aCache, css::SheetLoadData& aData, 110 nsTArray<RefPtr<css::SheetLoadData>>& aDatasToNotify) { 111 if (aCache) { 112 aCache->LoadCompleted(aData); 113 } 114 115 // Go through and deal with the whole linked list. 116 auto* data = &aData; 117 auto* networkMetadata = aData.GetNetworkMetadata(); 118 do { 119 MOZ_RELEASE_ASSERT(!data->mSheetCompleteCalled); 120 data->mSheetCompleteCalled = true; 121 122 if (!data->mNetworkMetadata) { 123 data->mNetworkMetadata = networkMetadata; 124 } 125 126 if (!data->mSheetAlreadyComplete) { 127 // If mSheetAlreadyComplete, then the sheet could well be modified between 128 // when we posted the async call to SheetComplete and now, since the sheet 129 // was page-accessible during that whole time. 130 131 // HasForcedUniqueInner() is okay if the sheet is constructed, because 132 // constructed sheets are always unique and they may be set to complete 133 // multiple times if their rules are replaced via Replace() 134 MOZ_ASSERT(data->mSheet->IsConstructed() || 135 !data->mSheet->HasForcedUniqueInner(), 136 "should not get a forced unique inner during parsing"); 137 // Insert the sheet into the tree now the sheet has loaded, but only if 138 // the sheet is still relevant, and if this is a top-level sheet. 139 const bool needInsertIntoTree = [&] { 140 if (!data->mLoader->GetDocument()) { 141 // Not a document load, nothing to do. 142 return false; 143 } 144 if (data->IsPreload()) { 145 // Preloads are not supposed to be observable. 146 return false; 147 } 148 if (data->mSheet->IsConstructed()) { 149 // Constructable sheets are not in the regular stylesheet tree. 150 return false; 151 } 152 if (data->mIsChildSheet) { 153 // A child sheet, those will get exposed from the parent, no need to 154 // insert them into the tree. 155 return false; 156 } 157 if (data->mHadOwnerNode != !!data->mSheet->GetOwnerNode()) { 158 // The sheet was already removed from the tree and is no longer the 159 // current sheet of the owning node, we can bail. 160 return false; 161 } 162 return true; 163 }(); 164 165 if (needInsertIntoTree) { 166 data->mLoader->InsertSheetInTree(*data->mSheet); 167 } 168 data->mSheet->SetComplete(); 169 } else if (data->mSheet->IsApplicable()) { 170 if (dom::Document* doc = data->mLoader->GetDocument()) { 171 // We post these events for devtools, even though the applicable state 172 // has not actually changed, to make the cache not observable. 173 doc->PostStyleSheetApplicableStateChangeEvent(*data->mSheet); 174 } 175 } 176 aDatasToNotify.AppendElement(data); 177 178 NS_ASSERTION(!data->mParentData || data->mParentData->mPendingChildren != 0, 179 "Broken pending child count on our parent"); 180 181 // If we have a parent, our parent is no longer being parsed, and 182 // we are the last pending child, then our load completion 183 // completes the parent too. Note that the parent _can_ still be 184 // being parsed (eg if the child (us) failed to open the channel 185 // or some such). 186 if (data->mParentData && --(data->mParentData->mPendingChildren) == 0 && 187 !data->mParentData->mIsBeingParsed) { 188 LoadCompletedInternal(aCache, *data->mParentData, aDatasToNotify); 189 } 190 191 data = data->mNext; 192 } while (data); 193 194 if (aCache) { 195 aCache->InsertIfNeeded(aData); 196 } 197 } 198 199 size_t SharedStyleSheetCache::SizeOfIncludingThis( 200 MallocSizeOf aMallocSizeOf) const { 201 size_t n = aMallocSizeOf(this); 202 n += Base::SizeOfExcludingThis(aMallocSizeOf); 203 n += mInlineSheets.ShallowSizeOfExcludingThis(aMallocSizeOf); 204 for (const auto& sheetMap : mInlineSheets) { 205 for (const auto& entry : sheetMap.GetData()) { 206 n += entry.GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf); 207 n += entry.GetData().ShallowSizeOfExcludingThis(aMallocSizeOf); 208 for (const auto& candidate : entry.GetData()) { 209 n += candidate.mSheet->SizeOfIncludingThis(aMallocSizeOf); 210 } 211 } 212 } 213 return n; 214 } 215 216 NS_IMETHODIMP 217 SharedStyleSheetCache::CollectReports(nsIHandleReportCallback* aHandleReport, 218 nsISupports* aData, bool aAnonymize) { 219 MOZ_COLLECT_REPORT("explicit/layout/style-sheet-cache/document-shared", 220 KIND_HEAP, UNITS_BYTES, 221 SizeOfIncludingThis(SharedStyleSheetCacheMallocSizeOf), 222 "Memory used for SharedStyleSheetCache to share style " 223 "sheets across documents (not to be confused with " 224 "GlobalStyleSheetCache)"); 225 return NS_OK; 226 } 227 228 void SharedStyleSheetCache::ClearInProcess( 229 const Maybe<bool>& aChrome, const Maybe<nsCOMPtr<nsIPrincipal>>& aPrincipal, 230 const Maybe<nsCString>& aSchemelessSite, 231 const Maybe<OriginAttributesPattern>& aPattern, 232 const Maybe<nsCString>& aURL) { 233 Base::ClearInProcess(aChrome, aPrincipal, aSchemelessSite, aPattern, aURL); 234 if (!aChrome && !aPrincipal && !aSchemelessSite && !aURL) { 235 mInlineSheets.Clear(); 236 } 237 if (aURL) { 238 // Inline sheets don't have a URL. 239 return; 240 } 241 242 for (auto iter = mInlineSheets.Iter(); !iter.Done(); iter.Next()) { 243 if (SharedSubResourceCacheUtils::ShouldClearEntry( 244 nullptr, iter.Key(), iter.Key(), aChrome, aPrincipal, 245 aSchemelessSite, aPattern, aURL)) { 246 iter.Remove(); 247 } 248 } 249 } 250 251 void SharedStyleSheetCache::Clear( 252 const Maybe<bool>& aChrome, const Maybe<nsCOMPtr<nsIPrincipal>>& aPrincipal, 253 const Maybe<nsCString>& aSchemelessSite, 254 const Maybe<OriginAttributesPattern>& aPattern, 255 const Maybe<nsCString>& aURL) { 256 using ContentParent = dom::ContentParent; 257 258 if (XRE_IsParentProcess()) { 259 for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) { 260 (void)cp->SendClearStyleSheetCache(aChrome, aPrincipal, aSchemelessSite, 261 aPattern, aURL); 262 } 263 } 264 265 if (sSingleton) { 266 sSingleton->ClearInProcess(aChrome, aPrincipal, aSchemelessSite, aPattern, 267 aURL); 268 } 269 } 270 271 } // namespace mozilla 272 273 #undef LOG