1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
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/. */
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).
13 #include "nsStyleLinkElement.h"
15 #include "mozilla/CSSStyleSheet.h"
16 #include "mozilla/css/Loader.h"
17 #include "mozilla/dom/Element.h"
18 #include "mozilla/dom/FragmentOrElement.h"
19 #include "mozilla/dom/ShadowRoot.h"
20 #include "mozilla/Preferences.h"
21 #include "nsIContent.h"
22 #include "nsIDocument.h"
23 #include "nsIDOMComment.h"
24 #include "nsIDOMNode.h"
25 #include "nsIDOMStyleSheet.h"
26 #include "nsNetUtil.h"
27 #include "nsUnicharUtils.h"
29 #include "nsXPCOMCIDInternal.h"
30 #include "nsUnicharInputStream.h"
31 #include "nsContentUtils.h"
32 #include "nsStyleUtil.h"
34 using namespace mozilla
;
35 using namespace mozilla::dom
;
37 nsStyleLinkElement::nsStyleLinkElement()
38 : mDontLoadStyle(false)
39 , mUpdatesEnabled(true)
44 nsStyleLinkElement::~nsStyleLinkElement()
46 nsStyleLinkElement::SetStyleSheet(nullptr);
50 nsStyleLinkElement::Unlink()
52 mStyleSheet
= nullptr;
56 nsStyleLinkElement::Traverse(nsCycleCollectionTraversalCallback
&cb
)
58 nsStyleLinkElement
* tmp
= this;
59 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheet
);
63 nsStyleLinkElement::SetStyleSheet(CSSStyleSheet
* aStyleSheet
)
66 mStyleSheet
->SetOwningNode(nullptr);
69 mStyleSheet
= aStyleSheet
;
71 nsCOMPtr
<nsINode
> node
= do_QueryObject(this);
73 mStyleSheet
->SetOwningNode(node
);
80 NS_IMETHODIMP_(CSSStyleSheet
*)
81 nsStyleLinkElement::GetStyleSheet()
87 nsStyleLinkElement::InitStyleLinkElement(bool aDontLoadStyle
)
89 mDontLoadStyle
= aDontLoadStyle
;
95 nsStyleLinkElement::SetEnableUpdates(bool aEnableUpdates
)
97 mUpdatesEnabled
= aEnableUpdates
;
103 nsStyleLinkElement::GetCharset(nsAString
& aCharset
)
105 // descendants have to implement this themselves
106 return NS_ERROR_NOT_IMPLEMENTED
;
110 nsStyleLinkElement::OverrideBaseURI(nsIURI
* aNewBaseURI
)
112 NS_NOTREACHED("Base URI can't be overriden in this implementation "
113 "of nsIStyleSheetLinkingElement.");
117 nsStyleLinkElement::SetLineNumber(uint32_t aLineNumber
)
119 mLineNumber
= aLineNumber
;
123 nsStyleLinkElement::IsImportEnabled(nsIPrincipal
* aPrincipal
)
125 static bool sAdded
= false;
126 static bool sWebComponentsEnabled
;
128 // This part runs only once because of the static flag.
129 Preferences::AddBoolVarCache(&sWebComponentsEnabled
,
130 "dom.webcomponents.enabled",
135 if (sWebComponentsEnabled
) {
139 // If the web components pref is not enabled, check
140 // if we are in a certified app because imports is enabled
141 // for certified apps.
143 aPrincipal
->GetAppStatus() == nsIPrincipal::APP_STATUS_CERTIFIED
;
146 static uint32_t ToLinkMask(const nsAString
& aLink
, nsIPrincipal
* aPrincipal
)
148 if (aLink
.EqualsLiteral("prefetch"))
149 return nsStyleLinkElement::ePREFETCH
;
150 else if (aLink
.EqualsLiteral("dns-prefetch"))
151 return nsStyleLinkElement::eDNS_PREFETCH
;
152 else if (aLink
.EqualsLiteral("stylesheet"))
153 return nsStyleLinkElement::eSTYLESHEET
;
154 else if (aLink
.EqualsLiteral("next"))
155 return nsStyleLinkElement::eNEXT
;
156 else if (aLink
.EqualsLiteral("alternate"))
157 return nsStyleLinkElement::eALTERNATE
;
158 else if (aLink
.EqualsLiteral("import") && aPrincipal
&&
159 nsStyleLinkElement::IsImportEnabled(aPrincipal
))
160 return nsStyleLinkElement::eHTMLIMPORT
;
165 uint32_t nsStyleLinkElement::ParseLinkTypes(const nsAString
& aTypes
, nsIPrincipal
* aPrincipal
)
167 uint32_t linkMask
= 0;
168 nsAString::const_iterator start
, done
;
169 aTypes
.BeginReading(start
);
170 aTypes
.EndReading(done
);
174 nsAString::const_iterator
current(start
);
175 bool inString
= !nsContentUtils::IsHTMLWhitespace(*current
);
176 nsAutoString subString
;
178 while (current
!= done
) {
179 if (nsContentUtils::IsHTMLWhitespace(*current
)) {
181 nsContentUtils::ASCIIToLower(Substring(start
, current
), subString
);
182 linkMask
|= ToLinkMask(subString
, aPrincipal
);
195 nsContentUtils::ASCIIToLower(Substring(start
, current
), subString
);
196 linkMask
|= ToLinkMask(subString
, aPrincipal
);
202 nsStyleLinkElement::UpdateStyleSheet(nsICSSLoaderObserver
* aObserver
,
208 // We remove this stylesheet from the cache to load a new version.
209 nsCOMPtr
<nsIContent
> thisContent
;
210 CallQueryInterface(this, getter_AddRefs(thisContent
));
211 nsCOMPtr
<nsIDocument
> doc
= thisContent
->IsInShadowTree() ?
212 thisContent
->OwnerDoc() : thisContent
->GetUncomposedDoc();
213 if (doc
&& doc
->CSSLoader()->GetEnabled() &&
214 mStyleSheet
&& mStyleSheet
->GetOriginalURI()) {
215 doc
->CSSLoader()->ObsoleteSheet(mStyleSheet
->GetOriginalURI());
218 return DoUpdateStyleSheet(nullptr, nullptr, aObserver
, aWillNotify
,
219 aIsAlternate
, aForceReload
);
223 nsStyleLinkElement::UpdateStyleSheetInternal(nsIDocument
*aOldDocument
,
224 ShadowRoot
*aOldShadowRoot
,
227 bool notify
, alternate
;
228 return DoUpdateStyleSheet(aOldDocument
, aOldShadowRoot
, nullptr, ¬ify
,
229 &alternate
, aForceUpdate
);
233 IsScopedStyleElement(nsIContent
* aContent
)
235 // This is quicker than, say, QIing aContent to nsStyleLinkElement
236 // and then calling its virtual GetStyleSheetInfo method to find out
238 return (aContent
->IsHTML(nsGkAtoms::style
) ||
239 aContent
->IsSVG(nsGkAtoms::style
)) &&
240 aContent
->HasAttr(kNameSpaceID_None
, nsGkAtoms::scoped
);
244 HasScopedStyleSheetChild(nsIContent
* aContent
)
246 for (nsIContent
* n
= aContent
->GetFirstChild(); n
; n
= n
->GetNextSibling()) {
247 if (IsScopedStyleElement(n
)) {
254 // Called when aElement has had a <style scoped> child removed.
256 UpdateIsElementInStyleScopeFlagOnSubtree(Element
* aElement
)
258 NS_ASSERTION(aElement
->IsElementInStyleScope(),
259 "only call UpdateIsElementInStyleScopeFlagOnSubtree on a "
260 "subtree that has IsElementInStyleScope boolean flag set");
262 if (HasScopedStyleSheetChild(aElement
)) {
266 aElement
->ClearIsElementInStyleScope();
268 nsIContent
* n
= aElement
->GetNextNode(aElement
);
270 if (HasScopedStyleSheetChild(n
)) {
271 n
= n
->GetNextNonChildNode(aElement
);
273 if (n
->IsElement()) {
274 n
->ClearIsElementInStyleScope();
276 n
= n
->GetNextNode(aElement
);
282 GetScopeElement(nsIStyleSheet
* aSheet
)
284 nsRefPtr
<CSSStyleSheet
> cssStyleSheet
= do_QueryObject(aSheet
);
285 if (!cssStyleSheet
) {
289 return cssStyleSheet
->GetScopeElement();
293 nsStyleLinkElement::DoUpdateStyleSheet(nsIDocument
* aOldDocument
,
294 ShadowRoot
* aOldShadowRoot
,
295 nsICSSLoaderObserver
* aObserver
,
300 *aWillNotify
= false;
302 nsCOMPtr
<nsIContent
> thisContent
;
303 CallQueryInterface(this, getter_AddRefs(thisContent
));
305 // All instances of nsStyleLinkElement should implement nsIContent.
306 NS_ENSURE_TRUE(thisContent
, NS_ERROR_FAILURE
);
308 if (thisContent
->IsInAnonymousSubtree() &&
309 thisContent
->IsAnonymousContentInSVGUseSubtree()) {
310 // Stylesheets in <use>-cloned subtrees are disabled until we figure out
311 // how they should behave.
315 // Check for a ShadowRoot because link elements are inert in a
317 ShadowRoot
* containingShadow
= thisContent
->GetContainingShadow();
318 if (thisContent
->IsHTML(nsGkAtoms::link
) &&
319 (aOldShadowRoot
|| containingShadow
)) {
323 Element
* oldScopeElement
= GetScopeElement(mStyleSheet
);
325 if (mStyleSheet
&& (aOldDocument
|| aOldShadowRoot
)) {
326 MOZ_ASSERT(!(aOldDocument
&& aOldShadowRoot
),
327 "ShadowRoot content is never in document, thus "
328 "there should not be a old document and old "
329 "ShadowRoot simultaneously.");
331 // We're removing the link element from the document or shadow tree,
332 // unload the stylesheet. We want to do this even if updates are
333 // disabled, since otherwise a sheet with a stale linking element pointer
334 // will be hanging around -- not good!
335 if (aOldShadowRoot
) {
336 aOldShadowRoot
->RemoveSheet(mStyleSheet
);
338 aOldDocument
->BeginUpdate(UPDATE_STYLE
);
339 aOldDocument
->RemoveStyleSheet(mStyleSheet
);
340 aOldDocument
->EndUpdate(UPDATE_STYLE
);
343 nsStyleLinkElement::SetStyleSheet(nullptr);
344 if (oldScopeElement
) {
345 UpdateIsElementInStyleScopeFlagOnSubtree(oldScopeElement
);
349 // When static documents are created, stylesheets are cloned manually.
350 if (mDontLoadStyle
|| !mUpdatesEnabled
||
351 thisContent
->OwnerDoc()->IsStaticDocument()) {
355 nsCOMPtr
<nsIDocument
> doc
= thisContent
->IsInShadowTree() ?
356 thisContent
->OwnerDoc() : thisContent
->GetUncomposedDoc();
357 if (!doc
|| !doc
->CSSLoader()->GetEnabled()) {
362 nsCOMPtr
<nsIURI
> uri
= GetStyleSheetURL(&isInline
);
364 if (!aForceUpdate
&& mStyleSheet
&& !isInline
&& uri
) {
365 nsIURI
* oldURI
= mStyleSheet
->GetSheetURI();
368 nsresult rv
= oldURI
->Equals(uri
, &equal
);
369 if (NS_SUCCEEDED(rv
) && equal
) {
370 return NS_OK
; // We already loaded this stylesheet
376 if (thisContent
->IsInShadowTree()) {
377 ShadowRoot
* containingShadow
= thisContent
->GetContainingShadow();
378 containingShadow
->RemoveSheet(mStyleSheet
);
380 doc
->BeginUpdate(UPDATE_STYLE
);
381 doc
->RemoveStyleSheet(mStyleSheet
);
382 doc
->EndUpdate(UPDATE_STYLE
);
385 nsStyleLinkElement::SetStyleSheet(nullptr);
388 if (!uri
&& !isInline
) {
389 return NS_OK
; // If href is empty and this is not inline style then just bail
392 nsAutoString title
, type
, media
;
396 GetStyleSheetInfo(title
, type
, media
, &isScoped
, &isAlternate
);
398 if (!type
.LowerCaseEqualsLiteral("text/css")) {
402 Element
* scopeElement
= isScoped
? thisContent
->GetParentElement() : nullptr;
404 NS_ASSERTION(isInline
, "non-inline style must not have scope element");
405 scopeElement
->SetIsElementInStyleScopeFlagOnSubtree(true);
408 bool doneLoading
= false;
412 if (!nsContentUtils::GetNodeTextContent(thisContent
, false, text
)) {
413 return NS_ERROR_OUT_OF_MEMORY
;
416 MOZ_ASSERT(thisContent
->Tag() != nsGkAtoms::link
,
417 "<link> is not 'inline', and needs different CSP checks");
418 if (!nsStyleUtil::CSPAllowsInlineStyle(thisContent
,
419 thisContent
->NodePrincipal(),
420 doc
->GetDocumentURI(),
421 mLineNumber
, text
, &rv
))
424 // Parse the style sheet.
425 rv
= doc
->CSSLoader()->
426 LoadInlineStyle(thisContent
, text
, mLineNumber
, title
, media
,
427 scopeElement
, aObserver
, &doneLoading
, &isAlternate
);
430 // XXXbz clone the URI here to work around content policies modifying URIs.
431 nsCOMPtr
<nsIURI
> clonedURI
;
432 uri
->Clone(getter_AddRefs(clonedURI
));
433 NS_ENSURE_TRUE(clonedURI
, NS_ERROR_OUT_OF_MEMORY
);
434 rv
= doc
->CSSLoader()->
435 LoadStyleLink(thisContent
, clonedURI
, title
, media
, isAlternate
,
436 GetCORSMode(), doc
->GetReferrerPolicy(),
437 aObserver
, &isAlternate
);
439 // Don't propagate LoadStyleLink() errors further than this, since some
440 // consumers (e.g. nsXMLContentSink) will completely abort on innocuous
441 // things like a stylesheet load being blocked by the security system.
448 NS_ENSURE_SUCCESS(rv
, rv
);
450 *aWillNotify
= !doneLoading
;
451 *aIsAlternate
= isAlternate
;
457 nsStyleLinkElement::UpdateStyleSheetScopedness(bool aIsNowScoped
)
463 nsCOMPtr
<nsIContent
> thisContent
;
464 CallQueryInterface(this, getter_AddRefs(thisContent
));
466 Element
* oldScopeElement
= mStyleSheet
->GetScopeElement();
467 Element
* newScopeElement
= aIsNowScoped
?
468 thisContent
->GetParentElement() :
471 if (oldScopeElement
== newScopeElement
) {
475 nsIDocument
* document
= thisContent
->GetOwnerDocument();
477 if (thisContent
->IsInShadowTree()) {
478 ShadowRoot
* containingShadow
= thisContent
->GetContainingShadow();
479 containingShadow
->RemoveSheet(mStyleSheet
);
481 mStyleSheet
->SetScopeElement(newScopeElement
);
483 containingShadow
->InsertSheet(mStyleSheet
, thisContent
);
485 document
->BeginUpdate(UPDATE_STYLE
);
486 document
->RemoveStyleSheet(mStyleSheet
);
488 mStyleSheet
->SetScopeElement(newScopeElement
);
490 document
->AddStyleSheet(mStyleSheet
);
491 document
->EndUpdate(UPDATE_STYLE
);
494 if (oldScopeElement
) {
495 UpdateIsElementInStyleScopeFlagOnSubtree(oldScopeElement
);
497 if (newScopeElement
) {
498 newScopeElement
->SetIsElementInStyleScopeFlagOnSubtree(true);