1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 /*
8  * A base class which implements nsIStyleSheetLinkingElement and can
9  * be subclassed by various content nodes that want to load
10  * stylesheets (<style>, <link>, processing instructions, etc).
11  */
12 
13 #include "mozilla/dom/LinkStyle.h"
14 
15 #include "mozilla/StyleSheet.h"
16 #include "mozilla/StyleSheetInlines.h"
17 #include "mozilla/css/Loader.h"
18 #include "mozilla/dom/Element.h"
19 #include "mozilla/dom/FragmentOrElement.h"
20 #include "mozilla/dom/HTMLLinkElement.h"
21 #include "mozilla/dom/ShadowRoot.h"
22 #include "mozilla/dom/SRILogHelper.h"
23 #include "mozilla/Preferences.h"
24 #include "mozilla/StaticPrefs_dom.h"
25 #include "nsIContent.h"
26 #include "mozilla/dom/Document.h"
27 #include "nsUnicharUtils.h"
28 #include "nsCRT.h"
29 #include "nsXPCOMCIDInternal.h"
30 #include "nsUnicharInputStream.h"
31 #include "nsContentUtils.h"
32 #include "nsStyleUtil.h"
33 #include "nsQueryObject.h"
34 
35 namespace mozilla::dom {
36 
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)37 LinkStyle::SheetInfo::SheetInfo(
38     const Document& aDocument, nsIContent* aContent,
39     already_AddRefed<nsIURI> aURI,
40     already_AddRefed<nsIPrincipal> aTriggeringPrincipal,
41     already_AddRefed<nsIReferrerInfo> aReferrerInfo,
42     mozilla::CORSMode aCORSMode, const nsAString& aTitle,
43     const nsAString& aMedia, const nsAString& aIntegrity,
44     const nsAString& aNonce, HasAlternateRel aHasAlternateRel,
45     IsInline aIsInline, IsExplicitlyEnabled aIsExplicitlyEnabled)
46     : mContent(aContent),
47       mURI(aURI),
48       mTriggeringPrincipal(aTriggeringPrincipal),
49       mReferrerInfo(aReferrerInfo),
50       mCORSMode(aCORSMode),
51       mTitle(aTitle),
52       mMedia(aMedia),
53       mIntegrity(aIntegrity),
54       mNonce(aNonce),
55       mHasAlternateRel(aHasAlternateRel == HasAlternateRel::Yes),
56       mIsInline(aIsInline == IsInline::Yes),
57       mIsExplicitlyEnabled(aIsExplicitlyEnabled) {
58   MOZ_ASSERT(!mIsInline || aContent);
59   MOZ_ASSERT_IF(aContent, aContent->OwnerDoc() == &aDocument);
60   MOZ_ASSERT(mReferrerInfo);
61   MOZ_ASSERT(mIntegrity.IsEmpty() || !mIsInline,
62              "Integrity only applies to <link>");
63 }
64 
65 LinkStyle::SheetInfo::~SheetInfo() = default;
66 
LinkStyle()67 LinkStyle::LinkStyle()
68     : mUpdatesEnabled(true), mLineNumber(1), mColumnNumber(1) {}
69 
~LinkStyle()70 LinkStyle::~LinkStyle() { LinkStyle::SetStyleSheet(nullptr); }
71 
GetSheetForBindings() const72 StyleSheet* LinkStyle::GetSheetForBindings() const {
73   if (mStyleSheet && mStyleSheet->IsComplete()) {
74     return mStyleSheet;
75   }
76   return nullptr;
77 }
78 
GetTitleAndMediaForElement(const Element & aSelf,nsString & aTitle,nsString & aMedia)79 void LinkStyle::GetTitleAndMediaForElement(const Element& aSelf,
80                                            nsString& aTitle, nsString& aMedia) {
81   // Only honor title as stylesheet name for elements in the document (that is,
82   // ignore for Shadow DOM), per [1] and [2]. See [3].
83   //
84   // [1]: https://html.spec.whatwg.org/#attr-link-title
85   // [2]: https://html.spec.whatwg.org/#attr-style-title
86   // [3]: https://github.com/w3c/webcomponents/issues/535
87   if (aSelf.IsInUncomposedDoc()) {
88     aSelf.GetAttr(kNameSpaceID_None, nsGkAtoms::title, aTitle);
89     aTitle.CompressWhitespace();
90   }
91 
92   aSelf.GetAttr(kNameSpaceID_None, nsGkAtoms::media, aMedia);
93   // The HTML5 spec is formulated in terms of the CSSOM spec, which specifies
94   // that media queries should be ASCII lowercased during serialization.
95   //
96   // FIXME(emilio): How does it matter? This is going to be parsed anyway, CSS
97   // should take care of serializing it properly.
98   nsContentUtils::ASCIIToLower(aMedia);
99 }
100 
IsCSSMimeTypeAttributeForStyleElement(const Element & aSelf)101 bool LinkStyle::IsCSSMimeTypeAttributeForStyleElement(const Element& aSelf) {
102   // Per
103   // https://html.spec.whatwg.org/multipage/semantics.html#the-style-element:update-a-style-block
104   // step 4, for style elements we should only accept empty and "text/css" type
105   // attribute values.
106   nsAutoString type;
107   aSelf.GetAttr(kNameSpaceID_None, nsGkAtoms::type, type);
108   return type.IsEmpty() || type.LowerCaseEqualsLiteral("text/css");
109 }
110 
Unlink()111 void LinkStyle::Unlink() { LinkStyle::SetStyleSheet(nullptr); }
112 
Traverse(nsCycleCollectionTraversalCallback & cb)113 void LinkStyle::Traverse(nsCycleCollectionTraversalCallback& cb) {
114   LinkStyle* tmp = this;
115   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheet);
116 }
117 
SetStyleSheet(StyleSheet * aStyleSheet)118 void LinkStyle::SetStyleSheet(StyleSheet* aStyleSheet) {
119   if (mStyleSheet) {
120     mStyleSheet->SetOwningNode(nullptr);
121   }
122 
123   mStyleSheet = aStyleSheet;
124   if (mStyleSheet) {
125     mStyleSheet->SetOwningNode(&AsContent());
126   }
127 }
128 
GetCharset(nsAString & aCharset)129 void LinkStyle::GetCharset(nsAString& aCharset) { aCharset.Truncate(); }
130 
ToLinkMask(const nsAString & aLink)131 static uint32_t ToLinkMask(const nsAString& aLink) {
132   // Keep this in sync with sRelValues in HTMLLinkElement.cpp
133   if (aLink.EqualsLiteral("prefetch"))
134     return LinkStyle::ePREFETCH;
135   else if (aLink.EqualsLiteral("dns-prefetch"))
136     return LinkStyle::eDNS_PREFETCH;
137   else if (aLink.EqualsLiteral("stylesheet"))
138     return LinkStyle::eSTYLESHEET;
139   else if (aLink.EqualsLiteral("next"))
140     return LinkStyle::eNEXT;
141   else if (aLink.EqualsLiteral("alternate"))
142     return LinkStyle::eALTERNATE;
143   else if (aLink.EqualsLiteral("preconnect"))
144     return LinkStyle::ePRECONNECT;
145   else if (aLink.EqualsLiteral("preload"))
146     return LinkStyle::ePRELOAD;
147   else
148     return 0;
149 }
150 
ParseLinkTypes(const nsAString & aTypes)151 uint32_t LinkStyle::ParseLinkTypes(const nsAString& aTypes) {
152   uint32_t linkMask = 0;
153   nsAString::const_iterator start, done;
154   aTypes.BeginReading(start);
155   aTypes.EndReading(done);
156   if (start == done) return linkMask;
157 
158   nsAString::const_iterator current(start);
159   bool inString = !nsContentUtils::IsHTMLWhitespace(*current);
160   nsAutoString subString;
161 
162   while (current != done) {
163     if (nsContentUtils::IsHTMLWhitespace(*current)) {
164       if (inString) {
165         nsContentUtils::ASCIIToLower(Substring(start, current), subString);
166         linkMask |= ToLinkMask(subString);
167         inString = false;
168       }
169     } else {
170       if (!inString) {
171         start = current;
172         inString = true;
173       }
174     }
175     ++current;
176   }
177   if (inString) {
178     nsContentUtils::ASCIIToLower(Substring(start, current), subString);
179     linkMask |= ToLinkMask(subString);
180   }
181   return linkMask;
182 }
183 
UpdateStyleSheet(nsICSSLoaderObserver * aObserver)184 Result<LinkStyle::Update, nsresult> LinkStyle::UpdateStyleSheet(
185     nsICSSLoaderObserver* aObserver) {
186   return DoUpdateStyleSheet(nullptr, nullptr, aObserver, ForceUpdate::No);
187 }
188 
UpdateStyleSheetInternal(Document * aOldDocument,ShadowRoot * aOldShadowRoot,ForceUpdate aForceUpdate)189 Result<LinkStyle::Update, nsresult> LinkStyle::UpdateStyleSheetInternal(
190     Document* aOldDocument, ShadowRoot* aOldShadowRoot,
191     ForceUpdate aForceUpdate) {
192   return DoUpdateStyleSheet(aOldDocument, aOldShadowRoot, nullptr,
193                             aForceUpdate);
194 }
195 
DoUpdateStyleSheet(Document * aOldDocument,ShadowRoot * aOldShadowRoot,nsICSSLoaderObserver * aObserver,ForceUpdate aForceUpdate)196 Result<LinkStyle::Update, nsresult> LinkStyle::DoUpdateStyleSheet(
197     Document* aOldDocument, ShadowRoot* aOldShadowRoot,
198     nsICSSLoaderObserver* aObserver, ForceUpdate aForceUpdate) {
199   nsIContent& thisContent = AsContent();
200   if (thisContent.IsInSVGUseShadowTree()) {
201     // Stylesheets in <use>-cloned subtrees are disabled until we figure out
202     // how they should behave.
203     return Update{};
204   }
205 
206   if (mStyleSheet && (aOldDocument || aOldShadowRoot)) {
207     MOZ_ASSERT(!(aOldDocument && aOldShadowRoot),
208                "ShadowRoot content is never in document, thus "
209                "there should not be a old document and old "
210                "ShadowRoot simultaneously.");
211 
212     // We're removing the link element from the document or shadow tree, unload
213     // the stylesheet.
214     //
215     // We want to do this even if updates are disabled, since otherwise a sheet
216     // with a stale linking element pointer will be hanging around -- not good!
217     if (mStyleSheet->IsComplete()) {
218       if (aOldShadowRoot) {
219         aOldShadowRoot->RemoveStyleSheet(*mStyleSheet);
220       } else {
221         aOldDocument->RemoveStyleSheet(*mStyleSheet);
222       }
223     }
224 
225     SetStyleSheet(nullptr);
226   }
227 
228   Document* doc = thisContent.GetComposedDoc();
229 
230   // Loader could be null during unlink, see bug 1425866.
231   if (!doc || !doc->CSSLoader() || !doc->CSSLoader()->GetEnabled()) {
232     return Update{};
233   }
234 
235   // When static documents are created, stylesheets are cloned manually.
236   if (!mUpdatesEnabled || doc->IsStaticDocument()) {
237     return Update{};
238   }
239 
240   Maybe<SheetInfo> info = GetStyleSheetInfo();
241   if (aForceUpdate == ForceUpdate::No && mStyleSheet && info &&
242       !info->mIsInline && info->mURI) {
243     if (nsIURI* oldURI = mStyleSheet->GetSheetURI()) {
244       bool equal;
245       nsresult rv = oldURI->Equals(info->mURI, &equal);
246       if (NS_SUCCEEDED(rv) && equal) {
247         return Update{};
248       }
249     }
250   }
251 
252   if (mStyleSheet) {
253     if (mStyleSheet->IsComplete()) {
254       if (thisContent.IsInShadowTree()) {
255         ShadowRoot* containingShadow = thisContent.GetContainingShadow();
256         // Could be null only during unlink.
257         if (MOZ_LIKELY(containingShadow)) {
258           containingShadow->RemoveStyleSheet(*mStyleSheet);
259         }
260       } else {
261         doc->RemoveStyleSheet(*mStyleSheet);
262       }
263     }
264 
265     SetStyleSheet(nullptr);
266   }
267 
268   if (!info) {
269     return Update{};
270   }
271 
272   if (!info->mURI && !info->mIsInline) {
273     // If href is empty and this is not inline style then just bail
274     return Update{};
275   }
276 
277   if (info->mIsInline) {
278     nsAutoString text;
279     if (!nsContentUtils::GetNodeTextContent(&thisContent, false, text,
280                                             fallible)) {
281       return Err(NS_ERROR_OUT_OF_MEMORY);
282     }
283 
284     MOZ_ASSERT(thisContent.NodeInfo()->NameAtom() != nsGkAtoms::link,
285                "<link> is not 'inline', and needs different CSP checks");
286     MOZ_ASSERT(thisContent.IsElement());
287     nsresult rv = NS_OK;
288     if (!nsStyleUtil::CSPAllowsInlineStyle(
289             thisContent.AsElement(), doc, info->mTriggeringPrincipal,
290             mLineNumber, mColumnNumber, text, &rv)) {
291       if (NS_FAILED(rv)) {
292         return Err(rv);
293       }
294       return Update{};
295     }
296 
297     // Parse the style sheet.
298     return doc->CSSLoader()->LoadInlineStyle(*info, text, mLineNumber,
299                                              aObserver);
300   }
301   if (thisContent.IsElement()) {
302     nsAutoString integrity;
303     thisContent.AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity,
304                                      integrity);
305     if (!integrity.IsEmpty()) {
306       MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
307               ("LinkStyle::DoUpdateStyleSheet, integrity=%s",
308                NS_ConvertUTF16toUTF8(integrity).get()));
309     }
310   }
311   auto resultOrError = doc->CSSLoader()->LoadStyleLink(*info, aObserver);
312   if (resultOrError.isErr()) {
313     // Don't propagate LoadStyleLink() errors further than this, since some
314     // consumers (e.g. nsXMLContentSink) will completely abort on innocuous
315     // things like a stylesheet load being blocked by the security system.
316     return Update{};
317   }
318   return resultOrError;
319 }
320 
321 }  // namespace mozilla::dom
322