diff options
Diffstat (limited to '')
-rw-r--r-- | dom/base/LinkStyle.cpp | 324 |
1 files changed, 324 insertions, 0 deletions
diff --git a/dom/base/LinkStyle.cpp b/dom/base/LinkStyle.cpp new file mode 100644 index 0000000000..7e106f1ff7 --- /dev/null +++ b/dom/base/LinkStyle.cpp @@ -0,0 +1,324 @@ +/* -*- 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 "mozilla/dom/LinkStyle.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 "mozilla/StaticPrefs_dom.h" +#include "nsIContent.h" +#include "mozilla/dom/Document.h" +#include "nsUnicharUtils.h" +#include "nsCRT.h" +#include "nsXPCOMCIDInternal.h" +#include "nsUnicharInputStream.h" +#include "nsContentUtils.h" +#include "nsStyleUtil.h" +#include "nsQueryObject.h" + +namespace mozilla::dom { + +LinkStyle::SheetInfo::SheetInfo( + const Document& aDocument, nsIContent* aContent, + already_AddRefed<nsIURI> aURI, + already_AddRefed<nsIPrincipal> aTriggeringPrincipal, + already_AddRefed<nsIReferrerInfo> aReferrerInfo, + mozilla::CORSMode aCORSMode, const nsAString& aTitle, + const nsAString& aMedia, const nsAString& aIntegrity, + const nsAString& aNonce, HasAlternateRel aHasAlternateRel, + IsInline aIsInline, IsExplicitlyEnabled aIsExplicitlyEnabled) + : mContent(aContent), + mURI(aURI), + mTriggeringPrincipal(aTriggeringPrincipal), + mReferrerInfo(aReferrerInfo), + mCORSMode(aCORSMode), + mTitle(aTitle), + mMedia(aMedia), + mIntegrity(aIntegrity), + mNonce(aNonce), + mHasAlternateRel(aHasAlternateRel == HasAlternateRel::Yes), + mIsInline(aIsInline == IsInline::Yes), + mIsExplicitlyEnabled(aIsExplicitlyEnabled) { + MOZ_ASSERT(!mIsInline || aContent); + MOZ_ASSERT_IF(aContent, aContent->OwnerDoc() == &aDocument); + MOZ_ASSERT(mReferrerInfo); + MOZ_ASSERT(mIntegrity.IsEmpty() || !mIsInline, + "Integrity only applies to <link>"); +} + +LinkStyle::SheetInfo::~SheetInfo() = default; + +LinkStyle::LinkStyle() + : mUpdatesEnabled(true), mLineNumber(1), mColumnNumber(1) {} + +LinkStyle::~LinkStyle() { LinkStyle::SetStyleSheet(nullptr); } + +StyleSheet* LinkStyle::GetSheetForBindings() const { + if (!StaticPrefs::dom_expose_incomplete_stylesheets() && mStyleSheet && + !mStyleSheet->IsComplete()) { + return nullptr; + } + return mStyleSheet; +} + +void LinkStyle::GetTitleAndMediaForElement(const Element& aSelf, + nsString& aTitle, nsString& aMedia) { + // Only honor title as stylesheet name for elements in the document (that is, + // ignore for Shadow DOM), per [1] and [2]. See [3]. + // + // [1]: https://html.spec.whatwg.org/#attr-link-title + // [2]: https://html.spec.whatwg.org/#attr-style-title + // [3]: https://github.com/w3c/webcomponents/issues/535 + if (aSelf.IsInUncomposedDoc()) { + aSelf.GetAttr(kNameSpaceID_None, nsGkAtoms::title, aTitle); + aTitle.CompressWhitespace(); + } + + aSelf.GetAttr(kNameSpaceID_None, nsGkAtoms::media, aMedia); + // The HTML5 spec is formulated in terms of the CSSOM spec, which specifies + // that media queries should be ASCII lowercased during serialization. + // + // FIXME(emilio): How does it matter? This is going to be parsed anyway, CSS + // should take care of serializing it properly. + nsContentUtils::ASCIIToLower(aMedia); +} + +bool LinkStyle::IsCSSMimeTypeAttributeForStyleElement(const Element& aSelf) { + // Per + // https://html.spec.whatwg.org/multipage/semantics.html#the-style-element:update-a-style-block + // step 4, for style elements we should only accept empty and "text/css" type + // attribute values. + nsAutoString type; + aSelf.GetAttr(kNameSpaceID_None, nsGkAtoms::type, type); + return type.IsEmpty() || type.LowerCaseEqualsLiteral("text/css"); +} + +void LinkStyle::Unlink() { LinkStyle::SetStyleSheet(nullptr); } + +void LinkStyle::Traverse(nsCycleCollectionTraversalCallback& cb) { + LinkStyle* tmp = this; + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheet); +} + +void LinkStyle::SetStyleSheet(StyleSheet* aStyleSheet) { + if (mStyleSheet) { + mStyleSheet->SetOwningNode(nullptr); + } + + mStyleSheet = aStyleSheet; + if (mStyleSheet) { + mStyleSheet->SetOwningNode(&AsContent()); + } +} + +void LinkStyle::GetCharset(nsAString& aCharset) { aCharset.Truncate(); } + +static uint32_t ToLinkMask(const nsAString& aLink) { + // Keep this in sync with sRelValues in HTMLLinkElement.cpp + if (aLink.EqualsLiteral("prefetch")) + return LinkStyle::ePREFETCH; + else if (aLink.EqualsLiteral("dns-prefetch")) + return LinkStyle::eDNS_PREFETCH; + else if (aLink.EqualsLiteral("stylesheet")) + return LinkStyle::eSTYLESHEET; + else if (aLink.EqualsLiteral("next")) + return LinkStyle::eNEXT; + else if (aLink.EqualsLiteral("alternate")) + return LinkStyle::eALTERNATE; + else if (aLink.EqualsLiteral("preconnect")) + return LinkStyle::ePRECONNECT; + else if (aLink.EqualsLiteral("preload")) + return LinkStyle::ePRELOAD; + else + return 0; +} + +uint32_t LinkStyle::ParseLinkTypes(const nsAString& aTypes) { + 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); + inString = false; + } + } else { + if (!inString) { + start = current; + inString = true; + } + } + ++current; + } + if (inString) { + nsContentUtils::ASCIIToLower(Substring(start, current), subString); + linkMask |= ToLinkMask(subString); + } + return linkMask; +} + +Result<LinkStyle::Update, nsresult> LinkStyle::UpdateStyleSheet( + nsICSSLoaderObserver* aObserver) { + return DoUpdateStyleSheet(nullptr, nullptr, aObserver, ForceUpdate::No); +} + +Result<LinkStyle::Update, nsresult> LinkStyle::UpdateStyleSheetInternal( + Document* aOldDocument, ShadowRoot* aOldShadowRoot, + ForceUpdate aForceUpdate) { + return DoUpdateStyleSheet(aOldDocument, aOldShadowRoot, nullptr, + aForceUpdate); +} + +Result<LinkStyle::Update, nsresult> LinkStyle::DoUpdateStyleSheet( + Document* aOldDocument, ShadowRoot* aOldShadowRoot, + nsICSSLoaderObserver* aObserver, ForceUpdate aForceUpdate) { + nsIContent& thisContent = AsContent(); + if (thisContent.IsInSVGUseShadowTree()) { + // Stylesheets in <use>-cloned subtrees are disabled until we figure out + // how they should behave. + return Update{}; + } + + 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 (mStyleSheet->IsComplete() || + StaticPrefs::dom_expose_incomplete_stylesheets()) { + if (aOldShadowRoot) { + aOldShadowRoot->RemoveStyleSheet(*mStyleSheet); + } else { + aOldDocument->RemoveStyleSheet(*mStyleSheet); + } + } + + SetStyleSheet(nullptr); + } + + Document* doc = thisContent.GetComposedDoc(); + + // Loader could be null during unlink, see bug 1425866. + if (!doc || !doc->CSSLoader() || !doc->CSSLoader()->GetEnabled()) { + return Update{}; + } + + // When static documents are created, stylesheets are cloned manually. + if (!mUpdatesEnabled || doc->IsStaticDocument()) { + return Update{}; + } + + Maybe<SheetInfo> info = GetStyleSheetInfo(); + if (aForceUpdate == ForceUpdate::No && mStyleSheet && info && + !info->mIsInline && info->mURI) { + if (nsIURI* oldURI = mStyleSheet->GetSheetURI()) { + bool equal; + nsresult rv = oldURI->Equals(info->mURI, &equal); + if (NS_SUCCEEDED(rv) && equal) { + return Update{}; + } + } + } + + if (mStyleSheet) { + if (mStyleSheet->IsComplete() || + StaticPrefs::dom_expose_incomplete_stylesheets()) { + if (thisContent.IsInShadowTree()) { + ShadowRoot* containingShadow = thisContent.GetContainingShadow(); + // Could be null only during unlink. + if (MOZ_LIKELY(containingShadow)) { + containingShadow->RemoveStyleSheet(*mStyleSheet); + } + } else { + doc->RemoveStyleSheet(*mStyleSheet); + } + } + + SetStyleSheet(nullptr); + } + + if (!info) { + return Update{}; + } + + if (!info->mURI && !info->mIsInline) { + // If href is empty and this is not inline style then just bail + return Update{}; + } + + if (info->mIsInline) { + nsAutoString text; + if (!nsContentUtils::GetNodeTextContent(&thisContent, false, text, + fallible)) { + return Err(NS_ERROR_OUT_OF_MEMORY); + } + + MOZ_ASSERT(thisContent.NodeInfo()->NameAtom() != nsGkAtoms::link, + "<link> is not 'inline', and needs different CSP checks"); + MOZ_ASSERT(thisContent.IsElement()); + nsresult rv = NS_OK; + if (!nsStyleUtil::CSPAllowsInlineStyle( + thisContent.AsElement(), doc, info->mTriggeringPrincipal, + mLineNumber, mColumnNumber, text, &rv)) { + if (NS_FAILED(rv)) { + return Err(rv); + } + return Update{}; + } + + // Parse the style sheet. + return doc->CSSLoader()->LoadInlineStyle(*info, text, mLineNumber, + aObserver); + } + if (thisContent.IsElement()) { + nsAutoString integrity; + thisContent.AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, + integrity); + if (!integrity.IsEmpty()) { + MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug, + ("LinkStyle::DoUpdateStyleSheet, integrity=%s", + NS_ConvertUTF16toUTF8(integrity).get())); + } + } + auto resultOrError = doc->CSSLoader()->LoadStyleLink(*info, aObserver); + if (resultOrError.isErr()) { + // 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. + return Update{}; + } + return resultOrError; +} + +} // namespace mozilla::dom |