Bug 1480146 - Add a cache for inline stylesheets without @import rules. r=heycam

Supporting @import was much more annoying (I had a patch but it became much more
complex than this), and seems other browsers don't do it either:

  https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/css/style_sheet_contents.cc?l=149&rcl=1999822baf4dc673088e65997fa07ad158f2e166
  https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/css/StyleSheetContents.cpp?rev=246490#L111

Also added a singleton perf test for <link> caching.

Maybe we should add some cache eviction here, but I'm not sure what'd be the
best policy here.

Differential Revision: https://phabricator.services.mozilla.com/D41564
This commit is contained in:
Emilio Cobos Álvarez
2019-08-16 10:56:23 +00:00
parent 7670c060f1
commit adb5ebe6d8
4 changed files with 126 additions and 31 deletions

View File

@@ -461,11 +461,30 @@ struct Loader::Sheets {
// The SheetLoadData pointers in mLoadingDatas below are weak references.
nsDataHashtable<SheetLoadDataHashKey, SheetLoadData*> mLoadingDatas;
nsRefPtrHashtable<nsStringHashKey, StyleSheet> mInlineSheets;
RefPtr<StyleSheet> LookupInline(const nsAString&);
// A cache hit or miss. It is a miss if the `StyleSheet` is null.
using CacheResult = Tuple<RefPtr<StyleSheet>, SheetState>;
CacheResult Lookup(SheetLoadDataHashKey&, bool aSyncLoad);
};
RefPtr<StyleSheet> Loader::Sheets::LookupInline(const nsAString& aBuffer) {
auto result = mInlineSheets.Lookup(aBuffer);
if (!result) {
return nullptr;
}
if (result.Data()->HasForcedUniqueInner()) {
// Remove it now that we know that we're never going to use this stylesheet
// again.
result.Remove();
return nullptr;
}
return result.Data()->Clone(nullptr, nullptr, nullptr, nullptr);
}
static void AssertComplete(const StyleSheet& aSheet) {
// This sheet came from the XUL cache or our per-document hashtable; it
// better be a complete sheet.
@@ -1825,6 +1844,7 @@ Result<Loader::LoadSheetResult, nsresult> Loader::LoadInlineStyle(
// Check IsAlternateSheet now, since it can mutate our document.
auto isAlternate = IsAlternateSheet(aInfo.mTitle, aInfo.mHasAlternateRel);
LOG((" Sheet is alternate: %d", static_cast<int>(isAlternate)));
// Use the document's base URL so that @import in the inline sheet picks up
// the right base.
@@ -1833,48 +1853,71 @@ Result<Loader::LoadSheetResult, nsresult> Loader::LoadInlineStyle(
nsIURI* originalURI = nullptr;
MOZ_ASSERT(aInfo.mIntegrity.IsEmpty());
auto sheet = MakeRefPtr<StyleSheet>(eAuthorSheetFeatures, aInfo.mCORSMode,
SRIMetadata{});
sheet->SetURIs(sheetURI, originalURI, baseURI);
nsCOMPtr<nsIReferrerInfo> referrerInfo =
ReferrerInfo::CreateForInternalCSSResources(aInfo.mContent->OwnerDoc());
sheet->SetReferrerInfo(referrerInfo);
nsIPrincipal* principal = aInfo.mContent->NodePrincipal();
if (aInfo.mTriggeringPrincipal) {
// The triggering principal may be an expanded principal, which is safe to
// use for URL security checks, but not as the loader principal for a
// stylesheet. So treat this as principal inheritance, and downgrade if
// necessary.
principal =
BasePrincipal::Cast(aInfo.mTriggeringPrincipal)->PrincipalToInherit();
// We only cache sheets if in shadow trees, since regular document sheets are
// likely to be unique.
const bool isWorthCaching = aInfo.mContent->IsInShadowTree();
RefPtr<StyleSheet> sheet;
if (isWorthCaching) {
if (!mSheets) {
mSheets = MakeUnique<Sheets>();
}
sheet = mSheets->LookupInline(aBuffer);
}
const bool sheetFromCache = !!sheet;
if (!sheet) {
sheet = MakeRefPtr<StyleSheet>(eAuthorSheetFeatures, aInfo.mCORSMode,
SRIMetadata{});
sheet->SetURIs(sheetURI, originalURI, baseURI);
nsCOMPtr<nsIReferrerInfo> referrerInfo =
ReferrerInfo::CreateForInternalCSSResources(aInfo.mContent->OwnerDoc());
sheet->SetReferrerInfo(referrerInfo);
// We never actually load this, so just set its principal directly
sheet->SetPrincipal(principal);
nsIPrincipal* principal = aInfo.mContent->NodePrincipal();
if (aInfo.mTriggeringPrincipal) {
// The triggering principal may be an expanded principal, which is safe to
// use for URL security checks, but not as the loader principal for a
// stylesheet. So treat this as principal inheritance, and downgrade if
// necessary.
principal =
BasePrincipal::Cast(aInfo.mTriggeringPrincipal)->PrincipalToInherit();
}
LOG((" Sheet is alternate: %d", static_cast<int>(isAlternate)));
// We never actually load this, so just set its principal directly
sheet->SetPrincipal(principal);
}
auto matched = PrepareSheet(*sheet, aInfo.mTitle, aInfo.mMedia, nullptr,
isAlternate, aInfo.mIsExplicitlyEnabled);
InsertSheetInTree(*sheet, aInfo.mContent);
auto data = MakeRefPtr<SheetLoadData>(
this, aInfo.mTitle, nullptr, sheet, false, owningElement, isAlternate,
matched, aObserver, nullptr, aInfo.mReferrerInfo, aInfo.mContent);
data->mLineNumber = aLineNumber;
// Parse completion releases the load data.
//
// Note that we need to parse synchronously, since the web expects that the
// effects of inline stylesheets are visible immediately (aside from
// @imports).
NS_ConvertUTF16toUTF8 utf8(aBuffer);
Completed completed = ParseSheet(utf8, *data, AllowAsyncParse::No);
if (completed == Completed::No) {
data->mMustNotify = true;
Completed completed;
if (sheetFromCache) {
MOZ_ASSERT(sheet->IsComplete());
completed = Completed::Yes;
} else {
auto data = MakeRefPtr<SheetLoadData>(
this, aInfo.mTitle, nullptr, sheet, false, owningElement, isAlternate,
matched, aObserver, nullptr, aInfo.mReferrerInfo, aInfo.mContent);
data->mLineNumber = aLineNumber;
// Parse completion releases the load data.
//
// Note that we need to parse synchronously, since the web expects that the
// effects of inline stylesheets are visible immediately (aside from
// @imports).
NS_ConvertUTF16toUTF8 utf8(aBuffer);
completed = ParseSheet(utf8, *data, AllowAsyncParse::No);
if (completed == Completed::Yes) {
// TODO(emilio): Try to cache sheets with @import rules, maybe?
if (isWorthCaching) {
mSheets->mInlineSheets.Put(aBuffer, sheet);
}
} else {
data->mMustNotify = true;
}
}
return LoadSheetResult{completed, isAlternate, matched};
}