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 "nsStyleLinkElement.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 "nsIContent.h"
25 #include "nsIDocument.h"
26 #include "nsIDOMComment.h"
27 #include "nsIDOMNode.h"
28 #include "nsIDOMStyleSheet.h"
29 #include "nsUnicharUtils.h"
30 #include "nsCRT.h"
31 #include "nsXPCOMCIDInternal.h"
32 #include "nsUnicharInputStream.h"
33 #include "nsContentUtils.h"
34 #include "nsStyleUtil.h"
35 #include "nsQueryObject.h"
36 
37 using namespace mozilla;
38 using namespace mozilla::dom;
39 
nsStyleLinkElement()40 nsStyleLinkElement::nsStyleLinkElement()
41   : mDontLoadStyle(false)
42   , mUpdatesEnabled(true)
43   , mLineNumber(1)
44 {
45 }
46 
~nsStyleLinkElement()47 nsStyleLinkElement::~nsStyleLinkElement()
48 {
49   nsStyleLinkElement::SetStyleSheet(nullptr);
50 }
51 
52 void
Unlink()53 nsStyleLinkElement::Unlink()
54 {
55   nsStyleLinkElement::SetStyleSheet(nullptr);
56 }
57 
58 void
Traverse(nsCycleCollectionTraversalCallback & cb)59 nsStyleLinkElement::Traverse(nsCycleCollectionTraversalCallback &cb)
60 {
61   nsStyleLinkElement* tmp = this;
62   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheet);
63 }
64 
65 NS_IMETHODIMP
SetStyleSheet(StyleSheet * aStyleSheet)66 nsStyleLinkElement::SetStyleSheet(StyleSheet* aStyleSheet)
67 {
68   if (mStyleSheet) {
69     mStyleSheet->SetOwningNode(nullptr);
70   }
71 
72   mStyleSheet = aStyleSheet;
73   if (mStyleSheet) {
74     nsCOMPtr<nsINode> node = do_QueryObject(this);
75     if (node) {
76       mStyleSheet->SetOwningNode(node);
77     }
78   }
79 
80   return NS_OK;
81 }
82 
NS_IMETHODIMP_(StyleSheet *)83 NS_IMETHODIMP_(StyleSheet*)
84 nsStyleLinkElement::GetStyleSheet()
85 {
86   return mStyleSheet;
87 }
88 
89 NS_IMETHODIMP
InitStyleLinkElement(bool aDontLoadStyle)90 nsStyleLinkElement::InitStyleLinkElement(bool aDontLoadStyle)
91 {
92   mDontLoadStyle = aDontLoadStyle;
93 
94   return NS_OK;
95 }
96 
97 NS_IMETHODIMP
SetEnableUpdates(bool aEnableUpdates)98 nsStyleLinkElement::SetEnableUpdates(bool aEnableUpdates)
99 {
100   mUpdatesEnabled = aEnableUpdates;
101 
102   return NS_OK;
103 }
104 
105 NS_IMETHODIMP
GetCharset(nsAString & aCharset)106 nsStyleLinkElement::GetCharset(nsAString& aCharset)
107 {
108   // descendants have to implement this themselves
109   return NS_ERROR_NOT_IMPLEMENTED;
110 }
111 
112 /* virtual */ void
OverrideBaseURI(nsIURI * aNewBaseURI)113 nsStyleLinkElement::OverrideBaseURI(nsIURI* aNewBaseURI)
114 {
115   NS_NOTREACHED("Base URI can't be overriden in this implementation "
116                 "of nsIStyleSheetLinkingElement.");
117 }
118 
119 /* virtual */ void
SetLineNumber(uint32_t aLineNumber)120 nsStyleLinkElement::SetLineNumber(uint32_t aLineNumber)
121 {
122   mLineNumber = aLineNumber;
123 }
124 
125 /* virtual */ uint32_t
GetLineNumber()126 nsStyleLinkElement::GetLineNumber()
127 {
128   return mLineNumber;
129 }
130 
131 /* static */ bool
IsImportEnabled()132 nsStyleLinkElement::IsImportEnabled()
133 {
134   static bool sAdded = false;
135   static bool sImportsEnabled;
136   if (!sAdded) {
137     // This part runs only once because of the static flag.
138     Preferences::AddBoolVarCache(&sImportsEnabled,
139                                  "dom.htmlimports.enabled",
140                                  false);
141     sAdded = true;
142   }
143 
144   return sImportsEnabled;
145 }
146 
ToLinkMask(const nsAString & aLink,nsIPrincipal * aPrincipal)147 static uint32_t ToLinkMask(const nsAString& aLink, nsIPrincipal* aPrincipal)
148 {
149   // Keep this in sync with sRelValues in HTMLLinkElement.cpp
150   if (aLink.EqualsLiteral("prefetch"))
151     return nsStyleLinkElement::ePREFETCH;
152   else if (aLink.EqualsLiteral("dns-prefetch"))
153     return nsStyleLinkElement::eDNS_PREFETCH;
154   else if (aLink.EqualsLiteral("stylesheet"))
155     return nsStyleLinkElement::eSTYLESHEET;
156   else if (aLink.EqualsLiteral("next"))
157     return nsStyleLinkElement::eNEXT;
158   else if (aLink.EqualsLiteral("alternate"))
159     return nsStyleLinkElement::eALTERNATE;
160   else if (aLink.EqualsLiteral("import") &&
161            nsStyleLinkElement::IsImportEnabled())
162     return nsStyleLinkElement::eHTMLIMPORT;
163   else if (aLink.EqualsLiteral("preconnect"))
164     return nsStyleLinkElement::ePRECONNECT;
165   else
166     return 0;
167 }
168 
ParseLinkTypes(const nsAString & aTypes,nsIPrincipal * aPrincipal)169 uint32_t nsStyleLinkElement::ParseLinkTypes(const nsAString& aTypes, nsIPrincipal* aPrincipal)
170 {
171   uint32_t linkMask = 0;
172   nsAString::const_iterator start, done;
173   aTypes.BeginReading(start);
174   aTypes.EndReading(done);
175   if (start == done)
176     return linkMask;
177 
178   nsAString::const_iterator current(start);
179   bool inString = !nsContentUtils::IsHTMLWhitespace(*current);
180   nsAutoString subString;
181 
182   while (current != done) {
183     if (nsContentUtils::IsHTMLWhitespace(*current)) {
184       if (inString) {
185         nsContentUtils::ASCIIToLower(Substring(start, current), subString);
186         linkMask |= ToLinkMask(subString, aPrincipal);
187         inString = false;
188       }
189     }
190     else {
191       if (!inString) {
192         start = current;
193         inString = true;
194       }
195     }
196     ++current;
197   }
198   if (inString) {
199     nsContentUtils::ASCIIToLower(Substring(start, current), subString);
200     linkMask |= ToLinkMask(subString, aPrincipal);
201   }
202   return linkMask;
203 }
204 
205 NS_IMETHODIMP
UpdateStyleSheet(nsICSSLoaderObserver * aObserver,bool * aWillNotify,bool * aIsAlternate,bool aForceReload)206 nsStyleLinkElement::UpdateStyleSheet(nsICSSLoaderObserver* aObserver,
207                                      bool* aWillNotify,
208                                      bool* aIsAlternate,
209                                      bool aForceReload)
210 {
211   if (aForceReload) {
212     // We remove this stylesheet from the cache to load a new version.
213     nsCOMPtr<nsIContent> thisContent;
214     CallQueryInterface(this, getter_AddRefs(thisContent));
215     nsCOMPtr<nsIDocument> doc = thisContent->IsInShadowTree() ?
216       thisContent->OwnerDoc() : thisContent->GetUncomposedDoc();
217     if (doc && doc->CSSLoader()->GetEnabled() &&
218         mStyleSheet && !mStyleSheet->IsInline()) {
219       doc->CSSLoader()->ObsoleteSheet(mStyleSheet->GetOriginalURI());
220     }
221   }
222   return DoUpdateStyleSheet(nullptr, nullptr, aObserver, aWillNotify,
223                             aIsAlternate, aForceReload);
224 }
225 
226 nsresult
UpdateStyleSheetInternal(nsIDocument * aOldDocument,ShadowRoot * aOldShadowRoot,bool aForceUpdate)227 nsStyleLinkElement::UpdateStyleSheetInternal(nsIDocument *aOldDocument,
228                                              ShadowRoot *aOldShadowRoot,
229                                              bool aForceUpdate)
230 {
231   bool notify, alternate;
232   return DoUpdateStyleSheet(aOldDocument, aOldShadowRoot, nullptr, &notify,
233                             &alternate, aForceUpdate);
234 }
235 
236 static bool
IsScopedStyleElement(nsIContent * aContent)237 IsScopedStyleElement(nsIContent* aContent)
238 {
239   // This is quicker than, say, QIing aContent to nsStyleLinkElement
240   // and then calling its virtual GetStyleSheetInfo method to find out
241   // if it is scoped.
242   return (aContent->IsHTMLElement(nsGkAtoms::style) ||
243           aContent->IsSVGElement(nsGkAtoms::style)) &&
244          aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::scoped);
245 }
246 
247 static bool
HasScopedStyleSheetChild(nsIContent * aContent)248 HasScopedStyleSheetChild(nsIContent* aContent)
249 {
250   for (nsIContent* n = aContent->GetFirstChild(); n; n = n->GetNextSibling()) {
251     if (IsScopedStyleElement(n)) {
252       return true;
253     }
254   }
255   return false;
256 }
257 
258 // Called when aElement has had a <style scoped> child removed.
259 static void
UpdateIsElementInStyleScopeFlagOnSubtree(Element * aElement)260 UpdateIsElementInStyleScopeFlagOnSubtree(Element* aElement)
261 {
262   NS_ASSERTION(aElement->IsElementInStyleScope(),
263                "only call UpdateIsElementInStyleScopeFlagOnSubtree on a "
264                "subtree that has IsElementInStyleScope boolean flag set");
265 
266   if (HasScopedStyleSheetChild(aElement)) {
267     return;
268   }
269 
270   aElement->ClearIsElementInStyleScope();
271 
272   nsIContent* n = aElement->GetNextNode(aElement);
273   while (n) {
274     if (HasScopedStyleSheetChild(n)) {
275       n = n->GetNextNonChildNode(aElement);
276     } else {
277       if (n->IsElement()) {
278         n->ClearIsElementInStyleScope();
279       }
280       n = n->GetNextNode(aElement);
281     }
282   }
283 }
284 
285 nsresult
DoUpdateStyleSheet(nsIDocument * aOldDocument,ShadowRoot * aOldShadowRoot,nsICSSLoaderObserver * aObserver,bool * aWillNotify,bool * aIsAlternate,bool aForceUpdate)286 nsStyleLinkElement::DoUpdateStyleSheet(nsIDocument* aOldDocument,
287                                        ShadowRoot* aOldShadowRoot,
288                                        nsICSSLoaderObserver* aObserver,
289                                        bool* aWillNotify,
290                                        bool* aIsAlternate,
291                                        bool aForceUpdate)
292 {
293   *aWillNotify = false;
294 
295   nsCOMPtr<nsIContent> thisContent;
296   CallQueryInterface(this, getter_AddRefs(thisContent));
297 
298   // All instances of nsStyleLinkElement should implement nsIContent.
299   NS_ENSURE_TRUE(thisContent, NS_ERROR_FAILURE);
300 
301   if (thisContent->IsInAnonymousSubtree() &&
302       thisContent->IsAnonymousContentInSVGUseSubtree()) {
303     // Stylesheets in <use>-cloned subtrees are disabled until we figure out
304     // how they should behave.
305     return NS_OK;
306   }
307 
308   // Check for a ShadowRoot because link elements are inert in a
309   // ShadowRoot.
310   ShadowRoot* containingShadow = thisContent->GetContainingShadow();
311   if (thisContent->IsHTMLElement(nsGkAtoms::link) &&
312       (aOldShadowRoot || containingShadow)) {
313     return NS_OK;
314   }
315 
316   // XXXheycam ServoStyleSheets do not support <style scoped>.
317   Element* oldScopeElement = nullptr;
318   if (mStyleSheet) {
319     if (mStyleSheet->IsServo()) {
320       NS_WARNING("stylo: ServoStyleSheets don't support <style scoped>");
321     } else {
322       oldScopeElement = mStyleSheet->AsGecko()->GetScopeElement();
323     }
324   }
325 
326   if (mStyleSheet && (aOldDocument || aOldShadowRoot)) {
327     MOZ_ASSERT(!(aOldDocument && aOldShadowRoot),
328                "ShadowRoot content is never in document, thus "
329                "there should not be a old document and old "
330                "ShadowRoot simultaneously.");
331 
332     // We're removing the link element from the document or shadow tree,
333     // unload the stylesheet.  We want to do this even if updates are
334     // disabled, since otherwise a sheet with a stale linking element pointer
335     // will be hanging around -- not good!
336     if (aOldShadowRoot) {
337       aOldShadowRoot->RemoveSheet(mStyleSheet);
338     } else {
339       aOldDocument->BeginUpdate(UPDATE_STYLE);
340       aOldDocument->RemoveStyleSheet(mStyleSheet);
341       aOldDocument->EndUpdate(UPDATE_STYLE);
342     }
343 
344     nsStyleLinkElement::SetStyleSheet(nullptr);
345     if (oldScopeElement) {
346       UpdateIsElementInStyleScopeFlagOnSubtree(oldScopeElement);
347     }
348   }
349 
350   // When static documents are created, stylesheets are cloned manually.
351   if (mDontLoadStyle || !mUpdatesEnabled ||
352       thisContent->OwnerDoc()->IsStaticDocument()) {
353     return NS_OK;
354   }
355 
356   nsCOMPtr<nsIDocument> doc = thisContent->IsInShadowTree() ?
357     thisContent->OwnerDoc() : thisContent->GetUncomposedDoc();
358   if (!doc || !doc->CSSLoader()->GetEnabled()) {
359     return NS_OK;
360   }
361 
362   bool isInline;
363   nsCOMPtr<nsIURI> uri = GetStyleSheetURL(&isInline);
364 
365   if (!aForceUpdate && mStyleSheet && !isInline && uri) {
366     nsIURI* oldURI = mStyleSheet->GetSheetURI();
367     if (oldURI) {
368       bool equal;
369       nsresult rv = oldURI->Equals(uri, &equal);
370       if (NS_SUCCEEDED(rv) && equal) {
371         return NS_OK; // We already loaded this stylesheet
372       }
373     }
374   }
375 
376   if (mStyleSheet) {
377     if (thisContent->IsInShadowTree()) {
378       ShadowRoot* containingShadow = thisContent->GetContainingShadow();
379       containingShadow->RemoveSheet(mStyleSheet);
380     } else {
381       doc->BeginUpdate(UPDATE_STYLE);
382       doc->RemoveStyleSheet(mStyleSheet);
383       doc->EndUpdate(UPDATE_STYLE);
384     }
385 
386     nsStyleLinkElement::SetStyleSheet(nullptr);
387   }
388 
389   if (!uri && !isInline) {
390     return NS_OK; // If href is empty and this is not inline style then just bail
391   }
392 
393   nsAutoString title, type, media;
394   bool isScoped;
395   bool isAlternate;
396 
397   GetStyleSheetInfo(title, type, media, &isScoped, &isAlternate);
398 
399   if (!type.LowerCaseEqualsLiteral("text/css")) {
400     return NS_OK;
401   }
402 
403   Element* scopeElement = isScoped ? thisContent->GetParentElement() : nullptr;
404   if (scopeElement) {
405     NS_ASSERTION(isInline, "non-inline style must not have scope element");
406     scopeElement->SetIsElementInStyleScopeFlagOnSubtree(true);
407   }
408 
409   bool doneLoading = false;
410   nsresult rv = NS_OK;
411   if (isInline) {
412     nsAutoString text;
413     if (!nsContentUtils::GetNodeTextContent(thisContent, false, text, fallible)) {
414       return NS_ERROR_OUT_OF_MEMORY;
415     }
416 
417     MOZ_ASSERT(thisContent->NodeInfo()->NameAtom() != nsGkAtoms::link,
418                "<link> is not 'inline', and needs different CSP checks");
419     if (!nsStyleUtil::CSPAllowsInlineStyle(thisContent,
420                                            thisContent->NodePrincipal(),
421                                            doc->GetDocumentURI(),
422                                            mLineNumber, text, &rv))
423       return rv;
424 
425     // Parse the style sheet.
426     rv = doc->CSSLoader()->
427       LoadInlineStyle(thisContent, text, mLineNumber, title, media,
428                       scopeElement, aObserver, &doneLoading, &isAlternate);
429   }
430   else {
431     nsAutoString integrity;
432     thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, integrity);
433     if (!integrity.IsEmpty()) {
434       MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
435               ("nsStyleLinkElement::DoUpdateStyleSheet, integrity=%s",
436                NS_ConvertUTF16toUTF8(integrity).get()));
437     }
438 
439     // if referrer attributes are enabled in preferences, load the link's referrer
440     // attribute. If the link does not provide a referrer attribute, ignore this
441     // and use the document's referrer policy
442 
443     net::ReferrerPolicy referrerPolicy = GetLinkReferrerPolicy();
444     if (referrerPolicy == net::RP_Unset) {
445       referrerPolicy = doc->GetReferrerPolicy();
446     }
447 
448     // XXXbz clone the URI here to work around content policies modifying URIs.
449     nsCOMPtr<nsIURI> clonedURI;
450     uri->Clone(getter_AddRefs(clonedURI));
451     NS_ENSURE_TRUE(clonedURI, NS_ERROR_OUT_OF_MEMORY);
452     rv = doc->CSSLoader()->
453       LoadStyleLink(thisContent, clonedURI, title, media, isAlternate,
454                     GetCORSMode(), referrerPolicy, integrity,
455                     aObserver, &isAlternate);
456     if (NS_FAILED(rv)) {
457       // Don't propagate LoadStyleLink() errors further than this, since some
458       // consumers (e.g. nsXMLContentSink) will completely abort on innocuous
459       // things like a stylesheet load being blocked by the security system.
460       doneLoading = true;
461       isAlternate = false;
462       rv = NS_OK;
463     }
464   }
465 
466   NS_ENSURE_SUCCESS(rv, rv);
467 
468   *aWillNotify = !doneLoading;
469   *aIsAlternate = isAlternate;
470 
471   return NS_OK;
472 }
473 
474 void
UpdateStyleSheetScopedness(bool aIsNowScoped)475 nsStyleLinkElement::UpdateStyleSheetScopedness(bool aIsNowScoped)
476 {
477   if (!mStyleSheet) {
478     return;
479   }
480 
481   if (mStyleSheet->IsServo()) {
482     // XXXheycam ServoStyleSheets don't support <style scoped>.
483     NS_ERROR("stylo: ServoStyleSheets don't support <style scoped>");
484     return;
485   }
486 
487   CSSStyleSheet* sheet = mStyleSheet->AsGecko();
488 
489   nsCOMPtr<nsIContent> thisContent;
490   CallQueryInterface(this, getter_AddRefs(thisContent));
491 
492   Element* oldScopeElement = sheet->GetScopeElement();
493   Element* newScopeElement = aIsNowScoped ?
494                                thisContent->GetParentElement() :
495                                nullptr;
496 
497   if (oldScopeElement == newScopeElement) {
498     return;
499   }
500 
501   nsIDocument* document = thisContent->GetOwnerDocument();
502 
503   if (thisContent->IsInShadowTree()) {
504     ShadowRoot* containingShadow = thisContent->GetContainingShadow();
505     containingShadow->RemoveSheet(mStyleSheet);
506 
507     sheet->SetScopeElement(newScopeElement);
508 
509     containingShadow->InsertSheet(mStyleSheet, thisContent);
510   } else {
511     document->BeginUpdate(UPDATE_STYLE);
512     document->RemoveStyleSheet(mStyleSheet);
513 
514     sheet->SetScopeElement(newScopeElement);
515 
516     document->AddStyleSheet(mStyleSheet);
517     document->EndUpdate(UPDATE_STYLE);
518   }
519 
520   if (oldScopeElement) {
521     UpdateIsElementInStyleScopeFlagOnSubtree(oldScopeElement);
522   }
523   if (newScopeElement) {
524     newScopeElement->SetIsElementInStyleScopeFlagOnSubtree(true);
525   }
526 }
527