diff options
Diffstat (limited to 'dom/base/nsStyleLinkElement.cpp')
-rw-r--r-- | dom/base/nsStyleLinkElement.cpp | 526 |
1 files changed, 526 insertions, 0 deletions
diff --git a/dom/base/nsStyleLinkElement.cpp b/dom/base/nsStyleLinkElement.cpp new file mode 100644 index 000000000..8ab2dab0b --- /dev/null +++ b/dom/base/nsStyleLinkElement.cpp @@ -0,0 +1,526 @@ +/* -*- 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 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * A base class which implements nsIStyleSheetLinkingElement and can + * be subclassed by various content nodes that want to load + * stylesheets (<style>, <link>, processing instructions, etc). + */ + +#include "nsStyleLinkElement.h" + +#include "mozilla/StyleSheet.h" +#include "mozilla/StyleSheetInlines.h" +#include "mozilla/css/Loader.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/FragmentOrElement.h" +#include "mozilla/dom/HTMLLinkElement.h" +#include "mozilla/dom/ShadowRoot.h" +#include "mozilla/dom/SRILogHelper.h" +#include "mozilla/Preferences.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIDOMComment.h" +#include "nsIDOMNode.h" +#include "nsIDOMStyleSheet.h" +#include "nsUnicharUtils.h" +#include "nsCRT.h" +#include "nsXPCOMCIDInternal.h" +#include "nsUnicharInputStream.h" +#include "nsContentUtils.h" +#include "nsStyleUtil.h" +#include "nsQueryObject.h" + +using namespace mozilla; +using namespace mozilla::dom; + +nsStyleLinkElement::nsStyleLinkElement() + : mDontLoadStyle(false) + , mUpdatesEnabled(true) + , mLineNumber(1) +{ +} + +nsStyleLinkElement::~nsStyleLinkElement() +{ + nsStyleLinkElement::SetStyleSheet(nullptr); +} + +void +nsStyleLinkElement::Unlink() +{ + nsStyleLinkElement::SetStyleSheet(nullptr); +} + +void +nsStyleLinkElement::Traverse(nsCycleCollectionTraversalCallback &cb) +{ + nsStyleLinkElement* tmp = this; + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheet); +} + +NS_IMETHODIMP +nsStyleLinkElement::SetStyleSheet(StyleSheet* aStyleSheet) +{ + if (mStyleSheet) { + mStyleSheet->SetOwningNode(nullptr); + } + + mStyleSheet = aStyleSheet; + if (mStyleSheet) { + nsCOMPtr<nsINode> node = do_QueryObject(this); + if (node) { + mStyleSheet->SetOwningNode(node); + } + } + + return NS_OK; +} + +NS_IMETHODIMP_(StyleSheet*) +nsStyleLinkElement::GetStyleSheet() +{ + return mStyleSheet; +} + +NS_IMETHODIMP +nsStyleLinkElement::InitStyleLinkElement(bool aDontLoadStyle) +{ + mDontLoadStyle = aDontLoadStyle; + + return NS_OK; +} + +NS_IMETHODIMP +nsStyleLinkElement::SetEnableUpdates(bool aEnableUpdates) +{ + mUpdatesEnabled = aEnableUpdates; + + return NS_OK; +} + +NS_IMETHODIMP +nsStyleLinkElement::GetCharset(nsAString& aCharset) +{ + // descendants have to implement this themselves + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* virtual */ void +nsStyleLinkElement::OverrideBaseURI(nsIURI* aNewBaseURI) +{ + NS_NOTREACHED("Base URI can't be overriden in this implementation " + "of nsIStyleSheetLinkingElement."); +} + +/* virtual */ void +nsStyleLinkElement::SetLineNumber(uint32_t aLineNumber) +{ + mLineNumber = aLineNumber; +} + +/* virtual */ uint32_t +nsStyleLinkElement::GetLineNumber() +{ + return mLineNumber; +} + +/* static */ bool +nsStyleLinkElement::IsImportEnabled() +{ + static bool sAdded = false; + static bool sImportsEnabled; + if (!sAdded) { + // This part runs only once because of the static flag. + Preferences::AddBoolVarCache(&sImportsEnabled, + "dom.htmlimports.enabled", + false); + sAdded = true; + } + + return sImportsEnabled; +} + +static uint32_t ToLinkMask(const nsAString& aLink, nsIPrincipal* aPrincipal) +{ + // Keep this in sync with sRelValues in HTMLLinkElement.cpp + if (aLink.EqualsLiteral("prefetch")) + return nsStyleLinkElement::ePREFETCH; + else if (aLink.EqualsLiteral("dns-prefetch")) + return nsStyleLinkElement::eDNS_PREFETCH; + else if (aLink.EqualsLiteral("stylesheet")) + return nsStyleLinkElement::eSTYLESHEET; + else if (aLink.EqualsLiteral("next")) + return nsStyleLinkElement::eNEXT; + else if (aLink.EqualsLiteral("alternate")) + return nsStyleLinkElement::eALTERNATE; + else if (aLink.EqualsLiteral("import") && + nsStyleLinkElement::IsImportEnabled()) + return nsStyleLinkElement::eHTMLIMPORT; + else if (aLink.EqualsLiteral("preconnect")) + return nsStyleLinkElement::ePRECONNECT; + else + return 0; +} + +uint32_t nsStyleLinkElement::ParseLinkTypes(const nsAString& aTypes, nsIPrincipal* aPrincipal) +{ + uint32_t linkMask = 0; + nsAString::const_iterator start, done; + aTypes.BeginReading(start); + aTypes.EndReading(done); + if (start == done) + return linkMask; + + nsAString::const_iterator current(start); + bool inString = !nsContentUtils::IsHTMLWhitespace(*current); + nsAutoString subString; + + while (current != done) { + if (nsContentUtils::IsHTMLWhitespace(*current)) { + if (inString) { + nsContentUtils::ASCIIToLower(Substring(start, current), subString); + linkMask |= ToLinkMask(subString, aPrincipal); + inString = false; + } + } + else { + if (!inString) { + start = current; + inString = true; + } + } + ++current; + } + if (inString) { + nsContentUtils::ASCIIToLower(Substring(start, current), subString); + linkMask |= ToLinkMask(subString, aPrincipal); + } + return linkMask; +} + +NS_IMETHODIMP +nsStyleLinkElement::UpdateStyleSheet(nsICSSLoaderObserver* aObserver, + bool* aWillNotify, + bool* aIsAlternate, + bool aForceReload) +{ + if (aForceReload) { + // We remove this stylesheet from the cache to load a new version. + nsCOMPtr<nsIContent> thisContent; + CallQueryInterface(this, getter_AddRefs(thisContent)); + nsCOMPtr<nsIDocument> doc = thisContent->IsInShadowTree() ? + thisContent->OwnerDoc() : thisContent->GetUncomposedDoc(); + if (doc && doc->CSSLoader()->GetEnabled() && + mStyleSheet && !mStyleSheet->IsInline()) { + doc->CSSLoader()->ObsoleteSheet(mStyleSheet->GetOriginalURI()); + } + } + return DoUpdateStyleSheet(nullptr, nullptr, aObserver, aWillNotify, + aIsAlternate, aForceReload); +} + +nsresult +nsStyleLinkElement::UpdateStyleSheetInternal(nsIDocument *aOldDocument, + ShadowRoot *aOldShadowRoot, + bool aForceUpdate) +{ + bool notify, alternate; + return DoUpdateStyleSheet(aOldDocument, aOldShadowRoot, nullptr, ¬ify, + &alternate, aForceUpdate); +} + +static bool +IsScopedStyleElement(nsIContent* aContent) +{ + // This is quicker than, say, QIing aContent to nsStyleLinkElement + // and then calling its virtual GetStyleSheetInfo method to find out + // if it is scoped. + return (aContent->IsHTMLElement(nsGkAtoms::style) || + aContent->IsSVGElement(nsGkAtoms::style)) && + aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::scoped); +} + +static bool +HasScopedStyleSheetChild(nsIContent* aContent) +{ + for (nsIContent* n = aContent->GetFirstChild(); n; n = n->GetNextSibling()) { + if (IsScopedStyleElement(n)) { + return true; + } + } + return false; +} + +// Called when aElement has had a <style scoped> child removed. +static void +UpdateIsElementInStyleScopeFlagOnSubtree(Element* aElement) +{ + NS_ASSERTION(aElement->IsElementInStyleScope(), + "only call UpdateIsElementInStyleScopeFlagOnSubtree on a " + "subtree that has IsElementInStyleScope boolean flag set"); + + if (HasScopedStyleSheetChild(aElement)) { + return; + } + + aElement->ClearIsElementInStyleScope(); + + nsIContent* n = aElement->GetNextNode(aElement); + while (n) { + if (HasScopedStyleSheetChild(n)) { + n = n->GetNextNonChildNode(aElement); + } else { + if (n->IsElement()) { + n->ClearIsElementInStyleScope(); + } + n = n->GetNextNode(aElement); + } + } +} + +nsresult +nsStyleLinkElement::DoUpdateStyleSheet(nsIDocument* aOldDocument, + ShadowRoot* aOldShadowRoot, + nsICSSLoaderObserver* aObserver, + bool* aWillNotify, + bool* aIsAlternate, + bool aForceUpdate) +{ + *aWillNotify = false; + + nsCOMPtr<nsIContent> thisContent; + CallQueryInterface(this, getter_AddRefs(thisContent)); + + // All instances of nsStyleLinkElement should implement nsIContent. + NS_ENSURE_TRUE(thisContent, NS_ERROR_FAILURE); + + if (thisContent->IsInAnonymousSubtree() && + thisContent->IsAnonymousContentInSVGUseSubtree()) { + // Stylesheets in <use>-cloned subtrees are disabled until we figure out + // how they should behave. + return NS_OK; + } + + // Check for a ShadowRoot because link elements are inert in a + // ShadowRoot. + ShadowRoot* containingShadow = thisContent->GetContainingShadow(); + if (thisContent->IsHTMLElement(nsGkAtoms::link) && + (aOldShadowRoot || containingShadow)) { + return NS_OK; + } + + // XXXheycam ServoStyleSheets do not support <style scoped>. + Element* oldScopeElement = nullptr; + if (mStyleSheet) { + if (mStyleSheet->IsServo()) { + NS_WARNING("stylo: ServoStyleSheets don't support <style scoped>"); + } else { + oldScopeElement = mStyleSheet->AsGecko()->GetScopeElement(); + } + } + + if (mStyleSheet && (aOldDocument || aOldShadowRoot)) { + MOZ_ASSERT(!(aOldDocument && aOldShadowRoot), + "ShadowRoot content is never in document, thus " + "there should not be a old document and old " + "ShadowRoot simultaneously."); + + // We're removing the link element from the document or shadow tree, + // unload the stylesheet. We want to do this even if updates are + // disabled, since otherwise a sheet with a stale linking element pointer + // will be hanging around -- not good! + if (aOldShadowRoot) { + aOldShadowRoot->RemoveSheet(mStyleSheet); + } else { + aOldDocument->BeginUpdate(UPDATE_STYLE); + aOldDocument->RemoveStyleSheet(mStyleSheet); + aOldDocument->EndUpdate(UPDATE_STYLE); + } + + nsStyleLinkElement::SetStyleSheet(nullptr); + if (oldScopeElement) { + UpdateIsElementInStyleScopeFlagOnSubtree(oldScopeElement); + } + } + + // When static documents are created, stylesheets are cloned manually. + if (mDontLoadStyle || !mUpdatesEnabled || + thisContent->OwnerDoc()->IsStaticDocument()) { + return NS_OK; + } + + nsCOMPtr<nsIDocument> doc = thisContent->IsInShadowTree() ? + thisContent->OwnerDoc() : thisContent->GetUncomposedDoc(); + if (!doc || !doc->CSSLoader()->GetEnabled()) { + return NS_OK; + } + + bool isInline; + nsCOMPtr<nsIURI> uri = GetStyleSheetURL(&isInline); + + if (!aForceUpdate && mStyleSheet && !isInline && uri) { + nsIURI* oldURI = mStyleSheet->GetSheetURI(); + if (oldURI) { + bool equal; + nsresult rv = oldURI->Equals(uri, &equal); + if (NS_SUCCEEDED(rv) && equal) { + return NS_OK; // We already loaded this stylesheet + } + } + } + + if (mStyleSheet) { + if (thisContent->IsInShadowTree()) { + ShadowRoot* containingShadow = thisContent->GetContainingShadow(); + containingShadow->RemoveSheet(mStyleSheet); + } else { + doc->BeginUpdate(UPDATE_STYLE); + doc->RemoveStyleSheet(mStyleSheet); + doc->EndUpdate(UPDATE_STYLE); + } + + nsStyleLinkElement::SetStyleSheet(nullptr); + } + + if (!uri && !isInline) { + return NS_OK; // If href is empty and this is not inline style then just bail + } + + nsAutoString title, type, media; + bool isScoped; + bool isAlternate; + + GetStyleSheetInfo(title, type, media, &isScoped, &isAlternate); + + if (!type.LowerCaseEqualsLiteral("text/css")) { + return NS_OK; + } + + Element* scopeElement = isScoped ? thisContent->GetParentElement() : nullptr; + if (scopeElement) { + NS_ASSERTION(isInline, "non-inline style must not have scope element"); + scopeElement->SetIsElementInStyleScopeFlagOnSubtree(true); + } + + bool doneLoading = false; + nsresult rv = NS_OK; + if (isInline) { + nsAutoString text; + if (!nsContentUtils::GetNodeTextContent(thisContent, false, text, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + MOZ_ASSERT(thisContent->NodeInfo()->NameAtom() != nsGkAtoms::link, + "<link> is not 'inline', and needs different CSP checks"); + if (!nsStyleUtil::CSPAllowsInlineStyle(thisContent, + thisContent->NodePrincipal(), + doc->GetDocumentURI(), + mLineNumber, text, &rv)) + return rv; + + // Parse the style sheet. + rv = doc->CSSLoader()-> + LoadInlineStyle(thisContent, text, mLineNumber, title, media, + scopeElement, aObserver, &doneLoading, &isAlternate); + } + else { + nsAutoString integrity; + thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, integrity); + if (!integrity.IsEmpty()) { + MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug, + ("nsStyleLinkElement::DoUpdateStyleSheet, integrity=%s", + NS_ConvertUTF16toUTF8(integrity).get())); + } + + // if referrer attributes are enabled in preferences, load the link's referrer + // attribute. If the link does not provide a referrer attribute, ignore this + // and use the document's referrer policy + + net::ReferrerPolicy referrerPolicy = GetLinkReferrerPolicy(); + if (referrerPolicy == net::RP_Unset) { + referrerPolicy = doc->GetReferrerPolicy(); + } + + // XXXbz clone the URI here to work around content policies modifying URIs. + nsCOMPtr<nsIURI> clonedURI; + uri->Clone(getter_AddRefs(clonedURI)); + NS_ENSURE_TRUE(clonedURI, NS_ERROR_OUT_OF_MEMORY); + rv = doc->CSSLoader()-> + LoadStyleLink(thisContent, clonedURI, title, media, isAlternate, + GetCORSMode(), referrerPolicy, integrity, + aObserver, &isAlternate); + if (NS_FAILED(rv)) { + // Don't propagate LoadStyleLink() errors further than this, since some + // consumers (e.g. nsXMLContentSink) will completely abort on innocuous + // things like a stylesheet load being blocked by the security system. + doneLoading = true; + isAlternate = false; + rv = NS_OK; + } + } + + NS_ENSURE_SUCCESS(rv, rv); + + *aWillNotify = !doneLoading; + *aIsAlternate = isAlternate; + + return NS_OK; +} + +void +nsStyleLinkElement::UpdateStyleSheetScopedness(bool aIsNowScoped) +{ + if (!mStyleSheet) { + return; + } + + if (mStyleSheet->IsServo()) { + // XXXheycam ServoStyleSheets don't support <style scoped>. + NS_ERROR("stylo: ServoStyleSheets don't support <style scoped>"); + return; + } + + CSSStyleSheet* sheet = mStyleSheet->AsGecko(); + + nsCOMPtr<nsIContent> thisContent; + CallQueryInterface(this, getter_AddRefs(thisContent)); + + Element* oldScopeElement = sheet->GetScopeElement(); + Element* newScopeElement = aIsNowScoped ? + thisContent->GetParentElement() : + nullptr; + + if (oldScopeElement == newScopeElement) { + return; + } + + nsIDocument* document = thisContent->GetOwnerDocument(); + + if (thisContent->IsInShadowTree()) { + ShadowRoot* containingShadow = thisContent->GetContainingShadow(); + containingShadow->RemoveSheet(mStyleSheet); + + sheet->SetScopeElement(newScopeElement); + + containingShadow->InsertSheet(mStyleSheet, thisContent); + } else { + document->BeginUpdate(UPDATE_STYLE); + document->RemoveStyleSheet(mStyleSheet); + + sheet->SetScopeElement(newScopeElement); + + document->AddStyleSheet(mStyleSheet); + document->EndUpdate(UPDATE_STYLE); + } + + if (oldScopeElement) { + UpdateIsElementInStyleScopeFlagOnSubtree(oldScopeElement); + } + if (newScopeElement) { + newScopeElement->SetIsElementInStyleScopeFlagOnSubtree(true); + } +} |