diff --git a/layout/inspector/inDOMUtils.cpp b/layout/inspector/inDOMUtils.cpp index b98a57e6781..4543dab7ab9 100644 --- a/layout/inspector/inDOMUtils.cpp +++ b/layout/inspector/inDOMUtils.cpp @@ -1259,7 +1259,7 @@ inDOMUtils::ParseStyleSheet(nsIDOMCSSStyleSheet *aSheet, nsRefPtr sheet = do_QueryObject(aSheet); NS_ENSURE_ARG_POINTER(sheet); - return sheet->ParseSheet(aInput); + return sheet->ReparseSheet(aInput); } NS_IMETHODIMP diff --git a/layout/inspector/tests/bug1202095-2.css b/layout/inspector/tests/bug1202095-2.css new file mode 100644 index 00000000000..a484814ebf0 --- /dev/null +++ b/layout/inspector/tests/bug1202095-2.css @@ -0,0 +1,7 @@ +/* + * Bug 1202095 - parseStyleSheet should not re-load @imports + */ + +body { + color: chartreuse; +} diff --git a/layout/inspector/tests/bug1202095.css b/layout/inspector/tests/bug1202095.css new file mode 100644 index 00000000000..fa8ef0feb63 --- /dev/null +++ b/layout/inspector/tests/bug1202095.css @@ -0,0 +1,7 @@ +/* + * Bug 1202095 - parseStyleSheet should not re-load @imports + */ + +body { + background-color: purple; +} diff --git a/layout/inspector/tests/mochitest.ini b/layout/inspector/tests/mochitest.ini index 71531dd2410..4dd9acdd3a9 100644 --- a/layout/inspector/tests/mochitest.ini +++ b/layout/inspector/tests/mochitest.ini @@ -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] diff --git a/layout/inspector/tests/test_parseStyleSheetImport.html b/layout/inspector/tests/test_parseStyleSheetImport.html new file mode 100644 index 00000000000..73fef2d51a9 --- /dev/null +++ b/layout/inspector/tests/test_parseStyleSheetImport.html @@ -0,0 +1,83 @@ + + + + + Test for Bug 1202095 + + + + + + + + diff --git a/layout/style/CSSStyleSheet.cpp b/layout/style/CSSStyleSheet.cpp index 4e5a6b9136a..a75af511d89 100644 --- a/layout/style/CSSStyleSheet.cpp +++ b/layout/style/CSSStyleSheet.cpp @@ -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 rule = mInner->mOrderedRules.ObjectAt(ruleCount - 1); mInner->mOrderedRules.RemoveObjectAt(ruleCount - 1); rule->SetStyleSheet(nullptr); + if (rule->GetType() == css::Rule::IMPORT_RULE) { + nsCOMPtr importRule(do_QueryInterface(rule)); + NS_ASSERTION(importRule, "GetType lied"); + + nsCOMPtr childSheet; + importRule->GetStyleSheet(getter_AddRefs(childSheet)); + + nsRefPtr 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); diff --git a/layout/style/CSSStyleSheet.h b/layout/style/CSSStyleSheet.h index d07b762d147..f426dde7088 100644 --- a/layout/style/CSSStyleSheet.h +++ b/layout/style/CSSStyleSheet.h @@ -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; } diff --git a/layout/style/Loader.cpp b/layout/style/Loader.cpp index 7439d6b4ca9..19bd6dedd46 100644 --- a/layout/style/Loader.cpp +++ b/layout/style/Loader.cpp @@ -507,6 +507,30 @@ SheetLoadData::ScheduleLoadEventIfNeeded(nsresult aStatus) } } +/********************* + * Style sheet reuse * + *********************/ + +bool +LoaderReusableStyleSheets::FindReusableStyleSheet(nsIURI* aURL, + nsRefPtr& 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 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); diff --git a/layout/style/Loader.h b/layout/style/Loader.h index f4da96e4c07..6da5285e815 100644 --- a/layout/style/Loader.h +++ b/layout/style/Loader.h @@ -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& 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> 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 diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 054709986e6..c798b0278e1 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -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(mImpl)-> ParseSheet(aInput, aSheetURI, aBaseURI, aSheetPrincipal, aLineNumber, - aAllowUnsafeRules); + aAllowUnsafeRules, aReusableSheets); } nsresult diff --git a/layout/style/nsCSSParser.h b/layout/style/nsCSSParser.h index 8be66ed8f5c..d5c1490d134 100644 --- a/layout/style/nsCSSParser.h +++ b/layout/style/nsCSSParser.h @@ -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