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, ¬ify,
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