Bug 1700051: part 48) Slightly simplify `mozInlineSpellWordUtil::FindRealWordContaini...
[gecko.git] / layout / style / SharedStyleSheetCache.cpp
blobc754e1c96bd0543a5905208392a8d422e8c81974
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/. */
7 #include "SharedStyleSheetCache.h"
9 #include "mozilla/MemoryReporting.h"
10 #include "mozilla/StyleSheet.h"
11 #include "mozilla/css/SheetLoadData.h"
12 #include "mozilla/dom/ContentParent.h"
13 #include "mozilla/dom/Document.h"
14 #include "mozilla/ServoBindings.h"
15 #include "nsContentUtils.h"
16 #include "nsXULPrototypeCache.h"
18 extern mozilla::LazyLogModule sCssLoaderLog;
20 #define LOG(args) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Debug, args)
22 namespace mozilla {
24 using css::SheetLoadData;
25 using SheetState = css::Loader::SheetState;
26 using LoadDataArray = css::Loader::LoadDataArray;
27 using IsAlternate = css::Loader::IsAlternate;
29 SharedStyleSheetCache* SharedStyleSheetCache::sInstance;
31 void SharedStyleSheetCache::Clear(nsIPrincipal* aForPrincipal) {
32 using ContentParent = dom::ContentParent;
34 if (XRE_IsParentProcess()) {
35 auto forPrincipal = aForPrincipal ? Some(RefPtr(aForPrincipal)) : Nothing();
36 for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
37 Unused << cp->SendClearStyleSheetCache(forPrincipal);
41 if (!sInstance) {
42 return;
45 if (!aForPrincipal) {
46 sInstance->mCompleteSheets.Clear();
47 return;
50 for (auto iter = sInstance->mCompleteSheets.Iter(); !iter.Done();
51 iter.Next()) {
52 if (iter.Key().Principal()->Equals(aForPrincipal)) {
53 iter.Remove();
58 already_AddRefed<SharedStyleSheetCache> SharedStyleSheetCache::Create() {
59 MOZ_DIAGNOSTIC_ASSERT(!sInstance);
60 RefPtr<SharedStyleSheetCache> cache = new SharedStyleSheetCache();
61 sInstance = cache.get();
62 RegisterWeakMemoryReporter(cache.get());
63 return cache.forget();
66 SharedStyleSheetCache::~SharedStyleSheetCache() {
67 MOZ_DIAGNOSTIC_ASSERT(sInstance == this);
68 UnregisterWeakMemoryReporter(this);
69 sInstance = nullptr;
72 NS_IMPL_ISUPPORTS(SharedStyleSheetCache, nsIMemoryReporter)
74 MOZ_DEFINE_MALLOC_SIZE_OF(SharedStyleSheetCacheMallocSizeOf)
76 NS_IMETHODIMP
77 SharedStyleSheetCache::CollectReports(nsIHandleReportCallback* aHandleReport,
78 nsISupports* aData, bool aAnonymize) {
79 MOZ_COLLECT_REPORT("explicit/layout/style-sheet-cache/document-shared",
80 KIND_HEAP, UNITS_BYTES,
81 SizeOfIncludingThis(SharedStyleSheetCacheMallocSizeOf),
82 "Memory used for SharedStyleSheetCache to share style "
83 "sheets across documents (not to be confused with "
84 "GlobalStyleSheetCache)");
85 return NS_OK;
88 static RefPtr<StyleSheet> CloneSheet(StyleSheet& aSheet) {
89 return aSheet.Clone(nullptr, nullptr, nullptr, nullptr);
92 static void AssertComplete(const StyleSheet& aSheet) {
93 // This sheet came from the XUL cache or SharedStyleSheetCache; it better be a
94 // complete sheet.
95 MOZ_ASSERT(aSheet.IsComplete(),
96 "Sheet thinks it's not complete while we think it is");
99 static void AssertIncompleteSheetMatches(const SheetLoadData& aData,
100 const SheetLoadDataHashKey& aKey) {
101 MOZ_ASSERT(aKey.Principal()->Equals(aData.mTriggeringPrincipal),
102 "Principals should be the same");
103 MOZ_ASSERT(!aData.mSheet->HasForcedUniqueInner(),
104 "CSSOM shouldn't allow access to incomplete sheets");
107 bool SharedStyleSheetCache::CompleteSheet::Expired() const {
108 return mExpirationTime &&
109 mExpirationTime <= nsContentUtils::SecondsFromPRTime(PR_Now());
112 SharedStyleSheetCache::CacheResult SharedStyleSheetCache::Lookup(
113 css::Loader& aLoader, const SheetLoadDataHashKey& aKey, bool aSyncLoad) {
114 nsIURI* uri = aKey.URI();
115 LOG(("SharedStyleSheetCache::Lookup(%s)", uri->GetSpecOrDefault().get()));
117 // Try to find first in the XUL prototype cache.
118 if (dom::IsChromeURI(uri)) {
119 nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
120 if (cache && cache->IsEnabled()) {
121 if (StyleSheet* sheet = cache->GetStyleSheet(uri)) {
122 LOG((" From XUL cache: %p", sheet));
123 AssertComplete(*sheet);
125 // See below, we always clone on insertion so we can guarantee the
126 // stylesheet is not modified.
127 MOZ_ASSERT(!sheet->HasForcedUniqueInner());
129 // We need to check the parsing mode manually because the XUL cache only
130 // keys off the URI. But we should fix that!
131 if (sheet->ParsingMode() == aKey.ParsingMode()) {
132 aLoader.DidHitCompleteSheetCache(aKey, nullptr);
133 return {CloneSheet(*sheet), SheetState::Complete};
136 LOG((" Not cloning due to mismatched parsing mode"));
141 // Now complete sheets.
142 if (auto lookup = mCompleteSheets.Lookup(aKey)) {
143 const CompleteSheet& completeSheet = lookup.Data();
144 // We can assert the stylesheet has not been modified, as we clone it on
145 // insertion.
146 StyleSheet& cachedSheet = *completeSheet.mSheet;
147 LOG((" From completed: %p, bypass: %d, expired: %d", &cachedSheet,
148 aLoader.ShouldBypassCache(), completeSheet.Expired()));
150 if ((!aLoader.ShouldBypassCache() && !completeSheet.Expired()) ||
151 aLoader.mLoadsPerformed.Contains(aKey)) {
152 LOG(
153 (" Not expired yet, or previously loaded already in "
154 "that document"));
156 AssertComplete(cachedSheet);
157 MOZ_ASSERT(cachedSheet.ParsingMode() == aKey.ParsingMode());
158 MOZ_ASSERT(!cachedSheet.HasForcedUniqueInner());
159 MOZ_ASSERT(!cachedSheet.HasModifiedRules());
161 RefPtr<StyleSheet> clone = CloneSheet(cachedSheet);
162 MOZ_ASSERT(!clone->HasForcedUniqueInner());
163 MOZ_ASSERT(!clone->HasModifiedRules());
165 aLoader.DidHitCompleteSheetCache(aKey, completeSheet.mUseCounters.get());
166 return {std::move(clone), SheetState::Complete};
170 if (aSyncLoad) {
171 return {};
174 if (SheetLoadData* data = mLoadingDatas.Get(aKey)) {
175 LOG((" From loading: %p", data->mSheet.get()));
176 AssertIncompleteSheetMatches(*data, aKey);
177 return {CloneSheet(*data->mSheet), SheetState::Loading};
180 if (SheetLoadData* data = mPendingDatas.GetWeak(aKey)) {
181 LOG((" From pending: %p", data->mSheet.get()));
182 AssertIncompleteSheetMatches(*data, aKey);
183 return {CloneSheet(*data->mSheet), SheetState::Pending};
186 return {};
189 void SharedStyleSheetCache::WillStartPendingLoad(SheetLoadData& aData) {
190 SheetLoadData* curr = &aData;
191 do {
192 MOZ_DIAGNOSTIC_ASSERT(curr->mLoader->mPendingLoadCount,
193 "Where did this pending load come from?");
194 --curr->mLoader->mPendingLoadCount;
195 } while ((curr = curr->mNext));
198 bool SharedStyleSheetCache::CoalesceLoad(const SheetLoadDataHashKey& aKey,
199 SheetLoadData& aNewLoad,
200 SheetState aExistingLoadState) {
201 MOZ_ASSERT(SheetLoadDataHashKey(aNewLoad).KeyEquals(aKey));
202 SheetLoadData* existingData = nullptr;
203 if (aExistingLoadState == SheetState::Loading) {
204 existingData = mLoadingDatas.Get(aKey);
205 MOZ_ASSERT(existingData, "CreateSheet lied about the state");
206 } else if (aExistingLoadState == SheetState::Pending) {
207 existingData = mPendingDatas.GetWeak(aKey);
208 MOZ_ASSERT(existingData, "CreateSheet lied about the state");
211 if (!existingData) {
212 return false;
215 if (aExistingLoadState == SheetState::Pending && !aNewLoad.ShouldDefer()) {
216 // Kick the load off; someone cares about it right away
217 RefPtr<SheetLoadData> removedData;
218 mPendingDatas.Remove(aKey, getter_AddRefs(removedData));
219 MOZ_ASSERT(removedData == existingData, "Bad loading table");
221 WillStartPendingLoad(*removedData);
223 // We insert to the front instead of the back, to keep the invariant that
224 // the front sheet always is the one that triggers the load.
225 aNewLoad.mNext = std::move(removedData);
226 LOG((" Forcing load of pending data"));
227 return false;
230 LOG((" Glomming on to existing load"));
231 SheetLoadData* data = existingData;
232 while (data->mNext) {
233 data = data->mNext;
235 data->mNext = &aNewLoad;
237 return true;
240 size_t SharedStyleSheetCache::SizeOfIncludingThis(
241 MallocSizeOf aMallocSizeOf) const {
242 size_t n = aMallocSizeOf(this);
244 n += mCompleteSheets.ShallowSizeOfExcludingThis(aMallocSizeOf);
245 for (const auto& data : mCompleteSheets.Values()) {
246 n += data.mSheet->SizeOfIncludingThis(aMallocSizeOf);
247 n += aMallocSizeOf(data.mUseCounters.get());
250 // Measurement of the following members may be added later if DMD finds it is
251 // worthwhile:
252 // - mLoadingDatas: transient, and should be small
253 // - mPendingDatas: transient, and should be small
254 return n;
257 void SharedStyleSheetCache::DeferSheetLoad(const SheetLoadDataHashKey& aKey,
258 SheetLoadData& aData) {
259 MOZ_ASSERT(SheetLoadDataHashKey(aData).KeyEquals(aKey));
260 MOZ_DIAGNOSTIC_ASSERT(!aData.mNext, "Should only defer loads once");
262 aData.mMustNotify = true;
263 mPendingDatas.InsertOrUpdate(aKey, RefPtr{&aData});
266 void SharedStyleSheetCache::LoadStarted(const SheetLoadDataHashKey& aKey,
267 SheetLoadData& aData) {
268 MOZ_ASSERT(aData.mURI, "No load required?");
269 MOZ_ASSERT(!aData.mIsLoading, "Already loading? How?");
270 MOZ_ASSERT(SheetLoadDataHashKey(aData).KeyEquals(aKey));
271 aData.mIsLoading = true;
272 mLoadingDatas.InsertOrUpdate(aKey, &aData);
275 void SharedStyleSheetCache::LoadCompleted(SharedStyleSheetCache* aCache,
276 SheetLoadData& aData,
277 nsresult aStatus) {
278 // If aStatus is a failure we need to mark this data failed. We also need to
279 // mark any ancestors of a failing data as failed and any sibling of a
280 // failing data as failed. Note that SheetComplete is never called on a
281 // SheetLoadData that is the mNext of some other SheetLoadData.
282 nsresult cancelledStatus = aStatus;
283 if (NS_FAILED(aStatus)) {
284 css::Loader::MarkLoadTreeFailed(aData);
285 } else {
286 cancelledStatus = NS_BINDING_ABORTED;
287 SheetLoadData* data = &aData;
288 do {
289 if (data->mIsCancelled) {
290 // We only need to mark loads for this loader as cancelled, so as to not
291 // fire error events in unrelated documents.
292 css::Loader::MarkLoadTreeFailed(*data, data->mLoader);
294 } while ((data = data->mNext));
297 // 8 is probably big enough for all our common cases. It's not likely that
298 // imports will nest more than 8 deep, and multiple sheets with the same URI
299 // are rare.
300 AutoTArray<RefPtr<SheetLoadData>, 8> datasToNotify;
301 LoadCompletedInternal(aCache, aData, datasToNotify);
303 // Now it's safe to go ahead and notify observers
304 for (RefPtr<SheetLoadData>& data : datasToNotify) {
305 auto status = data->mIsCancelled ? cancelledStatus : aStatus;
306 data->mLoader->NotifyObservers(*data, status);
310 void SharedStyleSheetCache::LoadCompletedInternal(
311 SharedStyleSheetCache* aCache, SheetLoadData& aData,
312 nsTArray<RefPtr<SheetLoadData>>& aDatasToNotify) {
313 if (aData.mIsLoading) {
314 MOZ_ASSERT(aCache);
315 SheetLoadDataHashKey key(aData);
316 Maybe<SheetLoadData*> loadingData = aCache->mLoadingDatas.Extract(key);
317 MOZ_DIAGNOSTIC_ASSERT(loadingData);
318 MOZ_DIAGNOSTIC_ASSERT(loadingData.value() == &aData);
319 Unused << loadingData;
320 aData.mIsLoading = false;
323 // Go through and deal with the whole linked list.
324 SheetLoadData* data = &aData;
325 do {
326 MOZ_DIAGNOSTIC_ASSERT(!data->mSheetCompleteCalled);
327 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
328 data->mSheetCompleteCalled = true;
329 #endif
331 if (!data->mSheetAlreadyComplete) {
332 // If mSheetAlreadyComplete, then the sheet could well be modified between
333 // when we posted the async call to SheetComplete and now, since the sheet
334 // was page-accessible during that whole time.
336 // HasForcedUniqueInner() is okay if the sheet is constructed, because
337 // constructed sheets are always unique and they may be set to complete
338 // multiple times if their rules are replaced via Replace()
339 MOZ_ASSERT(data->mSheet->IsConstructed() ||
340 !data->mSheet->HasForcedUniqueInner(),
341 "should not get a forced unique inner during parsing");
342 // Insert the sheet into the tree now the sheet has loaded, but only if
343 // the sheet is still relevant, and if this is a top-level sheet.
344 const bool needInsertIntoTree = [&] {
345 if (!data->mLoader->GetDocument()) {
346 // Not a document load, nothing to do.
347 return false;
349 if (data->IsPreload()) {
350 // Preloads are not supposed to be observable.
351 return false;
353 if (data->mSheet->IsConstructed()) {
354 // Constructable sheets are not in the regular stylesheet tree.
355 return false;
357 if (data->mIsChildSheet) {
358 // A child sheet, those will get exposed from the parent, no need to
359 // insert them into the tree.
360 return false;
362 if (data->mOwningNode != data->mSheet->GetOwnerNode()) {
363 // The sheet was already removed from the tree and is no longer the
364 // current sheet of the owning node, we can bail.
365 return false;
367 return true;
368 }();
370 if (needInsertIntoTree) {
371 data->mLoader->InsertSheetInTree(*data->mSheet, data->mOwningNode);
373 data->mSheet->SetComplete();
374 data->ScheduleLoadEventIfNeeded();
375 } else if (data->mSheet->IsApplicable()) {
376 if (dom::Document* doc = data->mLoader->GetDocument()) {
377 // We post these events for devtools, even though the applicable state
378 // has not actually changed, to make the cache not observable.
379 doc->PostStyleSheetApplicableStateChangeEvent(*data->mSheet);
383 aDatasToNotify.AppendElement(data);
385 NS_ASSERTION(!data->mParentData || data->mParentData->mPendingChildren != 0,
386 "Broken pending child count on our parent");
388 // If we have a parent, our parent is no longer being parsed, and
389 // we are the last pending child, then our load completion
390 // completes the parent too. Note that the parent _can_ still be
391 // being parsed (eg if the child (us) failed to open the channel
392 // or some such).
393 if (data->mParentData && --(data->mParentData->mPendingChildren) == 0 &&
394 !data->mParentData->mIsBeingParsed) {
395 LoadCompletedInternal(aCache, *data->mParentData, aDatasToNotify);
398 data = data->mNext;
399 } while (data);
401 if (aCache) {
402 aCache->InsertIntoCompleteCacheIfNeeded(aData);
406 void SharedStyleSheetCache::InsertIntoCompleteCacheIfNeeded(
407 SheetLoadData& aData) {
408 MOZ_ASSERT(aData.mLoader->GetDocument(),
409 "We only cache document-associated sheets");
410 LOG(("SharedStyleSheetCache::InsertIntoCompleteCacheIfNeeded"));
411 // If we ever start doing this for failed loads, we'll need to adjust the
412 // PostLoadEvent code that thinks anything already complete must have loaded
413 // succesfully.
414 if (aData.mLoadFailed) {
415 LOG((" Load failed, bailing"));
416 return;
419 // If this sheet came from the cache already, there's no need to override
420 // anything.
421 if (aData.mSheetAlreadyComplete) {
422 LOG((" Sheet came from the cache, bailing"));
423 return;
426 if (!aData.mURI) {
427 LOG((" Inline or constructable style sheet, bailing"));
428 // Inline sheet caching happens in Loader::mInlineSheets.
429 // Constructable sheets are not worth caching, they're always unique.
430 return;
433 // We need to clone the sheet on insertion to the cache because otherwise the
434 // stylesheets can keep alive full windows alive via either their JS wrapper,
435 // or via StyleSheet::mRelevantGlobal.
437 // If this ever changes, then you also need to fix up the memory reporting in
438 // both SizeOfIncludingThis and nsXULPrototypeCache::CollectMemoryReports.
439 RefPtr<StyleSheet> sheet = CloneSheet(*aData.mSheet);
441 if (dom::IsChromeURI(aData.mURI)) {
442 nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
443 if (cache && cache->IsEnabled()) {
444 if (!cache->GetStyleSheet(aData.mURI)) {
445 LOG((" Putting sheet in XUL prototype cache"));
446 NS_ASSERTION(sheet->IsComplete(),
447 "Should only be caching complete sheets");
449 // NOTE: If we stop cloning sheets before insertion, we need to change
450 // nsXULPrototypeCache::CollectMemoryReports() to stop using
451 // SizeOfIncludingThis() because it will no longer own the sheets.
452 cache->PutStyleSheet(std::move(sheet));
455 } else {
456 LOG((" Putting style sheet in shared cache: %s",
457 aData.mURI->GetSpecOrDefault().get()));
458 SheetLoadDataHashKey key(aData);
459 MOZ_ASSERT(sheet->IsComplete(), "Should only be caching complete sheets");
461 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
462 for (const auto& entry : mCompleteSheets) {
463 if (!key.KeyEquals(entry.GetKey())) {
464 MOZ_DIAGNOSTIC_ASSERT(entry.GetData().mSheet != sheet,
465 "Same sheet, different keys?");
466 } else {
467 MOZ_ASSERT(
468 entry.GetData().Expired() || aData.mLoader->ShouldBypassCache(),
469 "Overriding existing complete entry?");
472 #endif
474 UniquePtr<StyleUseCounters> counters;
475 if (aData.mUseCounters) {
476 // TODO(emilio): Servo_UseCounters_Clone() or something?
477 counters = Servo_UseCounters_Create().Consume();
478 Servo_UseCounters_Merge(counters.get(), aData.mUseCounters.get());
481 mCompleteSheets.InsertOrUpdate(
482 key, CompleteSheet{aData.mExpirationTime, std::move(counters),
483 std::move(sheet)});
487 void SharedStyleSheetCache::StartDeferredLoadsForLoader(
488 css::Loader& aLoader, StartLoads aStartLoads) {
489 using PendingLoad = css::Loader::PendingLoad;
491 LoadDataArray arr;
492 for (auto iter = mPendingDatas.Iter(); !iter.Done(); iter.Next()) {
493 bool startIt = false;
494 SheetLoadData* data = iter.Data();
495 do {
496 if (data->mLoader == &aLoader) {
497 // Note that we don't want to affect what the selected style set is, so
498 // use true for aHasAlternateRel.
499 if (aStartLoads != StartLoads::IfNonAlternate ||
500 aLoader.IsAlternateSheet(iter.Data()->mTitle, true) !=
501 IsAlternate::Yes) {
502 startIt = true;
503 break;
506 } while ((data = data->mNext));
507 if (startIt) {
508 arr.AppendElement(std::move(iter.Data()));
509 iter.Remove();
512 for (auto& data : arr) {
513 WillStartPendingLoad(*data);
514 data->mLoader->LoadSheet(*data, SheetState::NeedsParser, PendingLoad::Yes);
518 void SharedStyleSheetCache::CancelDeferredLoadsForLoader(css::Loader& aLoader) {
519 LoadDataArray arr;
521 for (auto iter = mPendingDatas.Iter(); !iter.Done(); iter.Next()) {
522 RefPtr<SheetLoadData>& first = iter.Data();
523 SheetLoadData* prev = nullptr;
524 SheetLoadData* current = iter.Data();
525 do {
526 if (current->mLoader != &aLoader) {
527 prev = current;
528 current = current->mNext;
529 continue;
531 // Detach the load from the list, mark it as cancelled, and then below
532 // call SheetComplete on it.
533 RefPtr<SheetLoadData> strong =
534 prev ? std::move(prev->mNext) : std::move(first);
535 MOZ_ASSERT(strong == current);
536 if (prev) {
537 prev->mNext = std::move(strong->mNext);
538 current = prev->mNext;
539 } else {
540 first = std::move(strong->mNext);
541 current = first;
543 strong->mIsCancelled = true;
544 arr.AppendElement(std::move(strong));
545 } while (current);
547 if (!first) {
548 iter.Remove();
552 for (auto& data : arr) {
553 aLoader.SheetComplete(*data, NS_BINDING_ABORTED);
557 void SharedStyleSheetCache::CancelLoadsForLoader(css::Loader& aLoader) {
558 CancelDeferredLoadsForLoader(aLoader);
560 // We can't stop in-progress loads because some other loader may care about
561 // them.
562 for (SheetLoadData* data : mLoadingDatas.Values()) {
563 MOZ_DIAGNOSTIC_ASSERT(data,
564 "We weren't properly notified and the load was "
565 "incorrectly dropped on the floor");
566 for (; data; data = data->mNext) {
567 if (data->mLoader == &aLoader) {
568 data->mIsCancelled = true;
574 void SharedStyleSheetCache::RegisterLoader(css::Loader& aLoader) {
575 MOZ_ASSERT(aLoader.GetDocument());
576 mLoaderPrincipalRefCnt.LookupOrInsert(aLoader.GetDocument()->NodePrincipal(),
577 0) += 1;
580 void SharedStyleSheetCache::UnregisterLoader(css::Loader& aLoader) {
581 MOZ_ASSERT(aLoader.GetDocument());
582 nsIPrincipal* prin = aLoader.GetDocument()->NodePrincipal();
583 auto lookup = mLoaderPrincipalRefCnt.Lookup(prin);
584 MOZ_RELEASE_ASSERT(lookup);
585 MOZ_RELEASE_ASSERT(lookup.Data());
586 if (!--lookup.Data()) {
587 lookup.Remove();
588 // TODO(emilio): Do this off a timer or something maybe.
589 for (auto iter = mCompleteSheets.Iter(); !iter.Done(); iter.Next()) {
590 if (iter.Key().LoaderPrincipal()->Equals(prin)) {
591 iter.Remove();
597 } // namespace mozilla
599 #undef LOG