Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#include "SharedStyleSheetCache.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/StoragePrincipalHelper.h"
#include "mozilla/StyleSheet.h"
#include "mozilla/css/SheetLoadData.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/Document.h"
#include "mozilla/ServoBindings.h"
#include "nsContentUtils.h"
#include "nsXULPrototypeCache.h"
extern mozilla::LazyLogModule sCssLoaderLog;
#define LOG(...) MOZ_LOG(sCssLoaderLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
namespace mozilla {
NS_IMPL_ISUPPORTS(SharedStyleSheetCache, nsIMemoryReporter)
MOZ_DEFINE_MALLOC_SIZE_OF(SharedStyleSheetCacheMallocSizeOf)
SharedStyleSheetCache::SharedStyleSheetCache() = default;
void SharedStyleSheetCache::Init() { RegisterWeakMemoryReporter(this); }
SharedStyleSheetCache::~SharedStyleSheetCache() {
UnregisterWeakMemoryReporter(this);
}
void SharedStyleSheetCache::LoadCompleted(SharedStyleSheetCache* aCache,
StyleSheetLoadData& aData,
nsresult aStatus) {
// If aStatus is a failure we need to mark this data failed. We also need to
// mark any ancestors of a failing data as failed and any sibling of a
// failing data as failed. Note that SheetComplete is never called on a
// SheetLoadData that is the mNext of some other SheetLoadData.
nsresult cancelledStatus = aStatus;
if (NS_FAILED(aStatus)) {
css::Loader::MarkLoadTreeFailed(aData);
} else {
cancelledStatus = NS_BINDING_ABORTED;
css::SheetLoadData* data = &aData;
do {
if (data->IsCancelled()) {
// We only need to mark loads for this loader as cancelled, so as to not
// fire error events in unrelated documents.
css::Loader::MarkLoadTreeFailed(*data, data->mLoader);
}
} while ((data = data->mNext));
}
// 8 is probably big enough for all our common cases. It's not likely that
// imports will nest more than 8 deep, and multiple sheets with the same URI
// are rare.
AutoTArray<RefPtr<css::SheetLoadData>, 8> datasToNotify;
LoadCompletedInternal(aCache, aData, datasToNotify);
// Now it's safe to go ahead and notify observers
for (RefPtr<css::SheetLoadData>& data : datasToNotify) {
auto status = data->IsCancelled() ? cancelledStatus : aStatus;
data->mLoader->NotifyObservers(*data, status);
}
}
void SharedStyleSheetCache::InsertIfNeeded(css::SheetLoadData& aData) {
MOZ_ASSERT(aData.mLoader->IsDocumentAssociated(),
"We only cache document-associated sheets");
LOG("SharedStyleSheetCache::InsertIfNeeded");
// 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 or constructable style sheet, bailing");
// Inline sheet caching happens in Loader::mInlineSheets, where we still
// have the input text available.
// Constructable sheets are not worth caching, they're always unique.
return;
}
LOG(" Putting style sheet in shared cache: %s",
aData.mURI->GetSpecOrDefault().get());
Insert(aData);
}
void SharedStyleSheetCache::LoadCompletedInternal(
SharedStyleSheetCache* aCache, css::SheetLoadData& aData,
nsTArray<RefPtr<css::SheetLoadData>>& aDatasToNotify) {
if (aCache) {
aCache->LoadCompleted(aData);
}
// Go through and deal with the whole linked list.
auto* data = &aData;
auto* networkMetadata = aData.GetNetworkMetadata();
do {
MOZ_RELEASE_ASSERT(!data->mSheetCompleteCalled);
data->mSheetCompleteCalled = true;
if (!data->mNetworkMetadata) {
data->mNetworkMetadata = networkMetadata;
}
if (!data->mSheetAlreadyComplete) {
// If mSheetAlreadyComplete, then the sheet could well be modified between
// when we posted the async call to SheetComplete and now, since the sheet
// was page-accessible during that whole time.
// HasForcedUniqueInner() is okay if the sheet is constructed, because
// constructed sheets are always unique and they may be set to complete
// multiple times if their rules are replaced via Replace()
MOZ_ASSERT(data->mSheet->IsConstructed() ||
!data->mSheet->HasForcedUniqueInner(),
"should not get a forced unique inner during parsing");
// Insert the sheet into the tree now the sheet has loaded, but only if
// the sheet is still relevant, and if this is a top-level sheet.
const bool needInsertIntoTree = [&] {
if (!data->mLoader->GetDocument()) {
// Not a document load, nothing to do.
return false;
}
if (data->IsPreload()) {
// Preloads are not supposed to be observable.
return false;
}
if (data->mSheet->IsConstructed()) {
// Constructable sheets are not in the regular stylesheet tree.
return false;
}
if (data->mIsChildSheet) {
// A child sheet, those will get exposed from the parent, no need to
// insert them into the tree.
return false;
}
if (data->mHadOwnerNode != !!data->mSheet->GetOwnerNode()) {
// The sheet was already removed from the tree and is no longer the
// current sheet of the owning node, we can bail.
return false;
}
return true;
}();
if (needInsertIntoTree) {
data->mLoader->InsertSheetInTree(*data->mSheet);
}
data->mSheet->SetComplete();
} else if (data->mSheet->IsApplicable()) {
if (dom::Document* doc = data->mLoader->GetDocument()) {
// We post these events for devtools, even though the applicable state
// has not actually changed, to make the cache not observable.
doc->PostStyleSheetApplicableStateChangeEvent(*data->mSheet);
}
}
aDatasToNotify.AppendElement(data);
NS_ASSERTION(!data->mParentData || data->mParentData->mPendingChildren != 0,
"Broken pending child count on our parent");
// If we have a parent, our parent is no longer being parsed, and
// we are the last pending child, then our load completion
// completes the parent too. Note that the parent _can_ still be
// being parsed (eg if the child (us) failed to open the channel
// or some such).
if (data->mParentData && --(data->mParentData->mPendingChildren) == 0 &&
!data->mParentData->mIsBeingParsed) {
LoadCompletedInternal(aCache, *data->mParentData, aDatasToNotify);
}
data = data->mNext;
} while (data);
if (aCache) {
aCache->InsertIfNeeded(aData);
}
}
NS_IMETHODIMP
SharedStyleSheetCache::CollectReports(nsIHandleReportCallback* aHandleReport,
nsISupports* aData, bool aAnonymize) {
MOZ_COLLECT_REPORT("explicit/layout/style-sheet-cache/document-shared",
KIND_HEAP, UNITS_BYTES,
SizeOfIncludingThis(SharedStyleSheetCacheMallocSizeOf),
"Memory used for SharedStyleSheetCache to share style "
"sheets across documents (not to be confused with "
"GlobalStyleSheetCache)");
return NS_OK;
}
void SharedStyleSheetCache::Clear(
const Maybe<nsCOMPtr<nsIPrincipal>>& aPrincipal,
const Maybe<nsCString>& aSchemelessSite,
const Maybe<OriginAttributesPattern>& aPattern) {
using ContentParent = dom::ContentParent;
if (XRE_IsParentProcess()) {
for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
Unused << cp->SendClearStyleSheetCache(aPrincipal, aSchemelessSite,
aPattern);
}
}
if (sSingleton) {
sSingleton->ClearInProcess(aPrincipal, aSchemelessSite, aPattern);
}
}
} // namespace mozilla
#undef LOG