Bug 1202095 - re-use @imported style sheets from inIDOMUtils.parseStyleSheet. r=heycam

This commit is contained in:
Tom Tromey 2015-09-18 07:27:00 +02:00
parent 0c3b5fe361
commit b383334103
11 changed files with 228 additions and 24 deletions

View File

@ -1259,7 +1259,7 @@ inDOMUtils::ParseStyleSheet(nsIDOMCSSStyleSheet *aSheet,
nsRefPtr<CSSStyleSheet> sheet = do_QueryObject(aSheet);
NS_ENSURE_ARG_POINTER(sheet);
return sheet->ParseSheet(aInput);
return sheet->ReparseSheet(aInput);
}
NS_IMETHODIMP

View File

@ -0,0 +1,7 @@
/*
* Bug 1202095 - parseStyleSheet should not re-load @imports
*/
body {
color: chartreuse;
}

View File

@ -0,0 +1,7 @@
/*
* Bug 1202095 - parseStyleSheet should not re-load @imports
*/
body {
background-color: purple;
}

View File

@ -1,5 +1,7 @@
[DEFAULT]
support-files =
bug1202095.css
bug1202095-2.css
bug856317.css
file_bug522601.html
@ -22,4 +24,5 @@ support-files =
[test_is_valid_css_color.html]
[test_isinheritableproperty.html]
[test_parseStyleSheet.html]
[test_parseStyleSheetImport.html]
[test_selectormatcheselement.html]

View File

@ -0,0 +1,83 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1202095
-->
<head>
<title>Test for Bug 1202095</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<style>
@import url('bug1202095.css');
@import url('bug1202095-2.css');
</style>
</head>
<body>
<script type="application/javascript">
var domUtils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"]
.getService(SpecialPowers.Ci.inIDOMUtils);
function do_test() {
var sheet = document.styleSheets[1];
var importRule = sheet.cssRules[0];
is(importRule.type, SpecialPowers.Ci.nsIDOMCSSRule.IMPORT_RULE,
"initial sheet has @import rule");
var importedSheet = importRule.styleSheet;
importedSheet.deleteRule(0);
is(importedSheet.cssRules.length, 0, "imported sheet now has no rules");
// "suffixed" refers to the "-2".
var suffixedSheet = sheet.cssRules[1].styleSheet;
domUtils.parseStyleSheet(suffixedSheet, "");
is(suffixedSheet.cssRules.length, 0, "second imported sheet now has no rules");
// Re-parse the style sheet, preserving the imports.
domUtils.parseStyleSheet(sheet, "@import url('bug1202095.css');" +
"@import url('bug1202095-2.css');");
is(sheet.cssRules[0].type, SpecialPowers.Ci.nsIDOMCSSRule.IMPORT_RULE,
"re-parsed sheet has @import rule");
is(sheet.cssRules[0].styleSheet, importedSheet,
"imported sheet has not changed");
is(sheet.cssRules[1].styleSheet, suffixedSheet,
"second imported sheet has not changed");
// Re-parse the style sheet, preserving both imports, but changing
// the order.
domUtils.parseStyleSheet(sheet, "@import url('bug1202095-2.css');" +
"@import url('bug1202095.css');");
is(sheet.cssRules[0].styleSheet, suffixedSheet,
"reordering preserved suffixed style sheet");
is(sheet.cssRules[1].styleSheet, importedSheet,
"reordering preserved unsuffixed style sheet");
// Re-parse the style sheet, removing the imports.
domUtils.parseStyleSheet(sheet, "");
is(sheet.cssRules.length, 0, "style sheet now has no rules");
// Re-parse the style sheet, adding one import back. This should
// not allow reuse.
domUtils.parseStyleSheet(sheet, "@import url('bug1202095.css');");
is(sheet.cssRules[0].type, SpecialPowers.Ci.nsIDOMCSSRule.IMPORT_RULE,
"re-re-re-parsed sheet has @import rule");
isnot(sheet.cssRules[0].styleSheet, importedSheet,
"imported sheet has changed now");
// Re-parse the style sheet, importing the same URL twice.
// The style sheet should be reused once, but not two times.
importedSheet = sheet.cssRules[0].styleSheet;
domUtils.parseStyleSheet(sheet, "@import url('bug1202095.css');" +
"@import url('bug1202095.css');");
is(sheet.cssRules[0].styleSheet, importedSheet,
"first imported sheet is reused");
isnot(sheet.cssRules[1].styleSheet, importedSheet,
"second imported sheet is reused");
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addLoadEvent(do_test);
</script>
</body>
</html>

View File

@ -2268,7 +2268,7 @@ CSSStyleSheet::StyleSheetLoaded(CSSStyleSheet* aSheet,
}
nsresult
CSSStyleSheet::ParseSheet(const nsAString& aInput)
CSSStyleSheet::ReparseSheet(const nsAString& aInput)
{
// Not doing this if the sheet is not complete!
if (!mInner->mComplete) {
@ -2290,21 +2290,37 @@ CSSStyleSheet::ParseSheet(const nsAString& aInput)
WillDirty();
// detach existing rules (including child sheets via import rules)
css::LoaderReusableStyleSheets reusableSheets;
int ruleCount;
while ((ruleCount = mInner->mOrderedRules.Count()) != 0) {
nsRefPtr<css::Rule> rule = mInner->mOrderedRules.ObjectAt(ruleCount - 1);
mInner->mOrderedRules.RemoveObjectAt(ruleCount - 1);
rule->SetStyleSheet(nullptr);
if (rule->GetType() == css::Rule::IMPORT_RULE) {
nsCOMPtr<nsIDOMCSSImportRule> importRule(do_QueryInterface(rule));
NS_ASSERTION(importRule, "GetType lied");
nsCOMPtr<nsIDOMCSSStyleSheet> childSheet;
importRule->GetStyleSheet(getter_AddRefs(childSheet));
nsRefPtr<CSSStyleSheet> cssSheet = do_QueryObject(childSheet);
if (cssSheet && cssSheet->GetOriginalURI()) {
reusableSheets.AddReusableSheet(cssSheet);
}
}
if (mDocument) {
mDocument->StyleRuleRemoved(this, rule);
}
}
// nuke child sheets list and current namespace map
for (CSSStyleSheet* child = mInner->mFirstChild; child; child = child->mNext) {
for (CSSStyleSheet* child = mInner->mFirstChild; child; ) {
NS_ASSERTION(child->mParent == this, "Child sheet is not parented to this!");
CSSStyleSheet* next = child->mNext;
child->mParent = nullptr;
child->mDocument = nullptr;
child->mNext = nullptr;
child = next;
}
mInner->mFirstChild = nullptr;
mInner->mNameSpaceMap = nullptr;
@ -2323,7 +2339,7 @@ CSSStyleSheet::ParseSheet(const nsAString& aInput)
nsCSSParser parser(loader, this);
nsresult rv = parser.ParseSheet(aInput, mInner->mSheetURI, mInner->mBaseURI,
mInner->mPrincipal, lineNumber,
allowUnsafeRules);
allowUnsafeRules, &reusableSheets);
DidDirty(); // we are always 'dirty' here since we always remove rules first
NS_ENSURE_SUCCESS(rv, rv);

View File

@ -242,7 +242,7 @@ public:
bool UseForPresentation(nsPresContext* aPresContext,
nsMediaQueryResultCacheKey& aKey) const;
nsresult ParseSheet(const nsAString& aInput);
nsresult ReparseSheet(const nsAString& aInput);
void SetInRuleProcessorCache() { mInRuleProcessorCache = true; }

View File

@ -507,6 +507,30 @@ SheetLoadData::ScheduleLoadEventIfNeeded(nsresult aStatus)
}
}
/*********************
* Style sheet reuse *
*********************/
bool
LoaderReusableStyleSheets::FindReusableStyleSheet(nsIURI* aURL,
nsRefPtr<CSSStyleSheet>& aResult)
{
MOZ_ASSERT(aURL);
for (size_t i = mReusableSheets.Length(); i > 0; --i) {
size_t index = i - 1;
bool sameURI;
MOZ_ASSERT(mReusableSheets[index]->GetOriginalURI());
nsresult rv = aURL->Equals(mReusableSheets[index]->GetOriginalURI(),
&sameURI);
if (!NS_FAILED(rv) && sameURI) {
aResult = mReusableSheets[index];
mReusableSheets.RemoveElementAt(index);
return true;
}
}
return false;
}
/*************************
* Loader Implementation *
*************************/
@ -2151,7 +2175,8 @@ nsresult
Loader::LoadChildSheet(CSSStyleSheet* aParentSheet,
nsIURI* aURL,
nsMediaList* aMedia,
ImportRule* aParentRule)
ImportRule* aParentRule,
LoaderReusableStyleSheets* aReusableSheets)
{
LOG(("css::Loader::LoadChildSheet"));
NS_PRECONDITION(aURL, "Must have a URI to load");
@ -2220,18 +2245,23 @@ Loader::LoadChildSheet(CSSStyleSheet* aParentSheet,
// Now that we know it's safe to load this (passes security check and not a
// loop) do so.
nsRefPtr<CSSStyleSheet> sheet;
bool isAlternate;
StyleSheetState state;
const nsSubstring& empty = EmptyString();
// For now, use CORS_NONE for child sheets
rv = CreateSheet(aURL, nullptr, principal, CORS_NONE,
aParentSheet->GetReferrerPolicy(),
EmptyString(), // integrity is only checked on main sheet
parentData ? parentData->mSyncLoad : false,
false, empty, state, &isAlternate, getter_AddRefs(sheet));
NS_ENSURE_SUCCESS(rv, rv);
if (aReusableSheets && aReusableSheets->FindReusableStyleSheet(aURL, sheet)) {
aParentRule->SetSheet(sheet);
state = eSheetComplete;
} else {
bool isAlternate;
const nsSubstring& empty = EmptyString();
// For now, use CORS_NONE for child sheets
rv = CreateSheet(aURL, nullptr, principal, CORS_NONE,
aParentSheet->GetReferrerPolicy(),
EmptyString(), // integrity is only checked on main sheet
parentData ? parentData->mSyncLoad : false,
false, empty, state, &isAlternate, getter_AddRefs(sheet));
NS_ENSURE_SUCCESS(rv, rv);
PrepareSheet(sheet, empty, empty, aMedia, nullptr, isAlternate);
PrepareSheet(sheet, empty, empty, aMedia, nullptr, isAlternate);
}
rv = InsertChildSheet(sheet, aParentSheet, aParentRule);
NS_ENSURE_SUCCESS(rv, rv);

View File

@ -20,6 +20,7 @@
#include "nsURIHashKey.h"
#include "mozilla/Attributes.h"
#include "mozilla/CORSMode.h"
#include "mozilla/CSSStyleSheet.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/net/ReferrerPolicy.h"
@ -30,7 +31,6 @@ class nsMediaList;
class nsIStyleSheetLinkingElement;
namespace mozilla {
class CSSStyleSheet;
namespace dom {
class Element;
} // namespace dom
@ -131,6 +131,47 @@ namespace css {
class SheetLoadData;
class ImportRule;
/*********************
* Style sheet reuse *
*********************/
class MOZ_RAII LoaderReusableStyleSheets
{
public:
LoaderReusableStyleSheets()
{
}
/**
* Look for a reusable sheet (see AddReusableSheet) matching the
* given URL. If found, set aResult, remove the reused sheet from
* the internal list, and return true. If not found, return false;
* in this case, aResult is not modified.
*
* @param aURL the url to match
* @param aResult [out] the style sheet which can be reused
*/
bool FindReusableStyleSheet(nsIURI* aURL, nsRefPtr<CSSStyleSheet>& aResult);
/**
* Indicate that a certain style sheet is available for reuse if its
* URI matches the URI of an @import. Sheets should be added in the
* opposite order in which they are intended to be reused.
*
* @param aSheet the sheet which can be reused
*/
void AddReusableSheet(CSSStyleSheet* aSheet) {
mReusableSheets.AppendElement(aSheet);
}
private:
LoaderReusableStyleSheets(const LoaderReusableStyleSheets&) = delete;
LoaderReusableStyleSheets& operator=(const LoaderReusableStyleSheets&) = delete;
// The sheets that can be reused.
nsTArray<nsRefPtr<CSSStyleSheet>> mReusableSheets;
};
/***********************************************************************
* Enum that describes the state of the sheet returned by CreateSheet. *
***********************************************************************/
@ -242,11 +283,14 @@ public:
* @param aMedia the already-parsed media list for the child sheet
* @param aRule the @import rule importing this child. This is used to
* properly order the child sheet list of aParentSheet.
* @param aSavedSheets any saved style sheets which could be reused
* for this load
*/
nsresult LoadChildSheet(CSSStyleSheet* aParentSheet,
nsIURI* aURL,
nsMediaList* aMedia,
ImportRule* aRule);
ImportRule* aRule,
LoaderReusableStyleSheets* aSavedSheets);
/**
* Synchronously load and return the stylesheet at aURL. Any child sheets

View File

@ -129,7 +129,8 @@ public:
nsIURI* aBaseURI,
nsIPrincipal* aSheetPrincipal,
uint32_t aLineNumber,
bool aAllowUnsafeRules);
bool aAllowUnsafeRules,
LoaderReusableStyleSheets* aReusableSheets);
nsresult ParseStyleAttribute(const nsAString& aAttributeValue,
nsIURI* aDocURL,
@ -1174,6 +1175,9 @@ protected:
// Used for @import rules
mozilla::css::Loader* mChildLoader; // not ref counted, it owns us
// Any sheets we may reuse when parsing an @import.
LoaderReusableStyleSheets* mReusableSheets;
// Sheet section we're in. This is used to enforce correct ordering of the
// various rule types (eg the fact that a @charset rule must come before
// anything else). Note that there are checks of similar things in various
@ -1319,6 +1323,7 @@ CSSParserImpl::CSSParserImpl()
mScanner(nullptr),
mReporter(nullptr),
mChildLoader(nullptr),
mReusableSheets(nullptr),
mSection(eCSSSection_Charset),
mNameSpaceMap(nullptr),
mHavePushBack(false),
@ -1431,7 +1436,8 @@ CSSParserImpl::ParseSheet(const nsAString& aInput,
nsIURI* aBaseURI,
nsIPrincipal* aSheetPrincipal,
uint32_t aLineNumber,
bool aAllowUnsafeRules)
bool aAllowUnsafeRules,
LoaderReusableStyleSheets* aReusableSheets)
{
NS_PRECONDITION(aSheetPrincipal, "Must have principal here!");
NS_PRECONDITION(aBaseURI, "need base URI");
@ -1477,6 +1483,7 @@ CSSParserImpl::ParseSheet(const nsAString& aInput,
}
mUnsafeRulesEnabled = aAllowUnsafeRules;
mReusableSheets = aReusableSheets;
nsCSSToken* tk = &mToken;
for (;;) {
@ -1500,6 +1507,7 @@ CSSParserImpl::ParseSheet(const nsAString& aInput,
ReleaseScanner();
mUnsafeRulesEnabled = false;
mReusableSheets = nullptr;
// XXX check for low level errors
return NS_OK;
@ -3440,7 +3448,7 @@ CSSParserImpl::ProcessImport(const nsString& aURLSpec,
}
if (mChildLoader) {
mChildLoader->LoadChildSheet(mSheet, url, aMedia, rule);
mChildLoader->LoadChildSheet(mSheet, url, aMedia, rule, mReusableSheets);
}
}
@ -15785,11 +15793,12 @@ nsCSSParser::ParseSheet(const nsAString& aInput,
nsIURI* aBaseURI,
nsIPrincipal* aSheetPrincipal,
uint32_t aLineNumber,
bool aAllowUnsafeRules)
bool aAllowUnsafeRules,
LoaderReusableStyleSheets* aReusableSheets)
{
return static_cast<CSSParserImpl*>(mImpl)->
ParseSheet(aInput, aSheetURI, aBaseURI, aSheetPrincipal, aLineNumber,
aAllowUnsafeRules);
aAllowUnsafeRules, aReusableSheets);
}
nsresult

View File

@ -33,6 +33,7 @@ namespace css {
class Rule;
class Declaration;
class Loader;
class LoaderReusableStyleSheets;
class StyleRule;
} // namespace css
} // namespace mozilla
@ -79,13 +80,17 @@ public:
* @param aLineNumber the line number of the first line of the sheet.
* @param aAllowUnsafeRules see aEnableUnsafeRules in
* mozilla::css::Loader::LoadSheetSync
* @param aReusableSheets style sheets that can be reused by an @import.
* This can be nullptr.
*/
nsresult ParseSheet(const nsAString& aInput,
nsIURI* aSheetURL,
nsIURI* aBaseURI,
nsIPrincipal* aSheetPrincipal,
uint32_t aLineNumber,
bool aAllowUnsafeRules);
bool aAllowUnsafeRules,
mozilla::css::LoaderReusableStyleSheets* aReusableSheets =
nullptr);
// Parse HTML style attribute or its equivalent in other markup
// languages. aBaseURL is the base url to use for relative links in