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