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/. */
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 "mozilla/dom/LinkStyle.h"
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/HTMLStyleElement.h"
22 #include "mozilla/dom/SVGStyleElement.h"
23 #include "mozilla/dom/ShadowRoot.h"
24 #include "mozilla/dom/SRILogHelper.h"
25 #include "mozilla/Preferences.h"
26 #include "mozilla/StaticPrefs_dom.h"
27 #include "nsIContent.h"
28 #include "mozilla/dom/Document.h"
29 #include "nsUnicharUtils.h"
31 #include "nsXPCOMCIDInternal.h"
32 #include "nsUnicharInputStream.h"
33 #include "nsContentUtils.h"
34 #include "nsStyleUtil.h"
35 #include "nsQueryObject.h"
37 namespace mozilla::dom
{
39 LinkStyle::SheetInfo::SheetInfo(
40 const Document
& aDocument
, nsIContent
* aContent
,
41 already_AddRefed
<nsIURI
> aURI
,
42 already_AddRefed
<nsIPrincipal
> aTriggeringPrincipal
,
43 already_AddRefed
<nsIReferrerInfo
> aReferrerInfo
,
44 mozilla::CORSMode aCORSMode
, const nsAString
& aTitle
,
45 const nsAString
& aMedia
, const nsAString
& aIntegrity
,
46 const nsAString
& aNonce
, HasAlternateRel aHasAlternateRel
,
47 IsInline aIsInline
, IsExplicitlyEnabled aIsExplicitlyEnabled
,
48 FetchPriority aFetchPriority
)
51 mTriggeringPrincipal(aTriggeringPrincipal
),
52 mReferrerInfo(aReferrerInfo
),
56 mIntegrity(aIntegrity
),
58 mFetchPriority(aFetchPriority
),
59 mHasAlternateRel(aHasAlternateRel
== HasAlternateRel::Yes
),
60 mIsInline(aIsInline
== IsInline::Yes
),
61 mIsExplicitlyEnabled(aIsExplicitlyEnabled
) {
62 MOZ_ASSERT(!mIsInline
|| aContent
);
63 MOZ_ASSERT_IF(aContent
, aContent
->OwnerDoc() == &aDocument
);
64 MOZ_ASSERT(mReferrerInfo
);
65 MOZ_ASSERT(mIntegrity
.IsEmpty() || !mIsInline
,
66 "Integrity only applies to <link>");
69 LinkStyle::SheetInfo::~SheetInfo() = default;
70 LinkStyle::LinkStyle() = default;
72 LinkStyle::~LinkStyle() { LinkStyle::SetStyleSheet(nullptr); }
74 StyleSheet
* LinkStyle::GetSheetForBindings() const {
75 if (mStyleSheet
&& mStyleSheet
->IsComplete()) {
81 void LinkStyle::GetTitleAndMediaForElement(const Element
& aSelf
,
82 nsString
& aTitle
, nsString
& aMedia
) {
83 // Only honor title as stylesheet name for elements in the document (that is,
84 // ignore for Shadow DOM), per [1] and [2]. See [3].
86 // [1]: https://html.spec.whatwg.org/#attr-link-title
87 // [2]: https://html.spec.whatwg.org/#attr-style-title
88 // [3]: https://github.com/w3c/webcomponents/issues/535
89 if (aSelf
.IsInUncomposedDoc()) {
90 aSelf
.GetAttr(nsGkAtoms::title
, aTitle
);
91 aTitle
.CompressWhitespace();
94 aSelf
.GetAttr(nsGkAtoms::media
, aMedia
);
95 // The HTML5 spec is formulated in terms of the CSSOM spec, which specifies
96 // that media queries should be ASCII lowercased during serialization.
98 // FIXME(emilio): How does it matter? This is going to be parsed anyway, CSS
99 // should take care of serializing it properly.
100 nsContentUtils::ASCIIToLower(aMedia
);
103 bool LinkStyle::IsCSSMimeTypeAttributeForStyleElement(const Element
& aSelf
) {
105 // https://html.spec.whatwg.org/multipage/semantics.html#the-style-element:update-a-style-block
106 // step 4, for style elements we should only accept empty and "text/css" type
109 aSelf
.GetAttr(nsGkAtoms::type
, type
);
110 return type
.IsEmpty() || type
.LowerCaseEqualsLiteral("text/css");
113 void LinkStyle::Unlink() { LinkStyle::SetStyleSheet(nullptr); }
115 void LinkStyle::Traverse(nsCycleCollectionTraversalCallback
& cb
) {
116 LinkStyle
* tmp
= this;
117 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheet
);
120 void LinkStyle::SetStyleSheet(StyleSheet
* aStyleSheet
) {
122 mStyleSheet
->SetOwningNode(nullptr);
125 mStyleSheet
= aStyleSheet
;
127 mStyleSheet
->SetOwningNode(&AsContent());
131 void LinkStyle::GetCharset(nsAString
& aCharset
) { aCharset
.Truncate(); }
133 static uint32_t ToLinkMask(const nsAString
& aLink
) {
134 // Keep this in sync with sSupportedRelValues in HTMLLinkElement.cpp
136 if (aLink
.EqualsLiteral("prefetch")) {
137 mask
= LinkStyle::ePREFETCH
;
138 } else if (aLink
.EqualsLiteral("dns-prefetch")) {
139 mask
= LinkStyle::eDNS_PREFETCH
;
140 } else if (aLink
.EqualsLiteral("stylesheet")) {
141 mask
= LinkStyle::eSTYLESHEET
;
142 } else if (aLink
.EqualsLiteral("next")) {
143 mask
= LinkStyle::eNEXT
;
144 } else if (aLink
.EqualsLiteral("alternate")) {
145 mask
= LinkStyle::eALTERNATE
;
146 } else if (aLink
.EqualsLiteral("preconnect")) {
147 mask
= LinkStyle::ePRECONNECT
;
148 } else if (aLink
.EqualsLiteral("preload")) {
149 mask
= LinkStyle::ePRELOAD
;
150 } else if (aLink
.EqualsLiteral("modulepreload")) {
151 mask
= LinkStyle::eMODULE_PRELOAD
;
157 uint32_t LinkStyle::ParseLinkTypes(const nsAString
& aTypes
) {
158 uint32_t linkMask
= 0;
159 nsAString::const_iterator start
, done
;
160 aTypes
.BeginReading(start
);
161 aTypes
.EndReading(done
);
162 if (start
== done
) return linkMask
;
164 nsAString::const_iterator
current(start
);
165 bool inString
= !nsContentUtils::IsHTMLWhitespace(*current
);
166 nsAutoString subString
;
168 while (current
!= done
) {
169 if (nsContentUtils::IsHTMLWhitespace(*current
)) {
171 nsContentUtils::ASCIIToLower(Substring(start
, current
), subString
);
172 linkMask
|= ToLinkMask(subString
);
184 nsContentUtils::ASCIIToLower(Substring(start
, current
), subString
);
185 linkMask
|= ToLinkMask(subString
);
190 Result
<LinkStyle::Update
, nsresult
> LinkStyle::UpdateStyleSheetInternal(
191 Document
* aOldDocument
, ShadowRoot
* aOldShadowRoot
,
192 ForceUpdate aForceUpdate
) {
193 return DoUpdateStyleSheet(aOldDocument
, aOldShadowRoot
, nullptr,
197 LinkStyle
* LinkStyle::FromNode(Element
& aElement
) {
198 nsAtom
* name
= aElement
.NodeInfo()->NameAtom();
199 if (name
== nsGkAtoms::link
) {
200 MOZ_ASSERT(aElement
.IsHTMLElement() == !!aElement
.AsLinkStyle());
201 return aElement
.IsHTMLElement() ? static_cast<HTMLLinkElement
*>(&aElement
)
204 if (name
== nsGkAtoms::style
) {
205 if (aElement
.IsHTMLElement()) {
206 MOZ_ASSERT(aElement
.AsLinkStyle());
207 return static_cast<HTMLStyleElement
*>(&aElement
);
209 if (aElement
.IsSVGElement()) {
210 MOZ_ASSERT(aElement
.AsLinkStyle());
211 return static_cast<SVGStyleElement
*>(&aElement
);
214 MOZ_ASSERT(!aElement
.AsLinkStyle());
218 void LinkStyle::BindToTree() {
219 if (mUpdatesEnabled
) {
220 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
221 "LinkStyle::BindToTree",
222 [this, pin
= RefPtr
{&AsContent()}] { UpdateStyleSheetInternal(); }));
226 Result
<LinkStyle::Update
, nsresult
> LinkStyle::DoUpdateStyleSheet(
227 Document
* aOldDocument
, ShadowRoot
* aOldShadowRoot
,
228 nsICSSLoaderObserver
* aObserver
, ForceUpdate aForceUpdate
) {
229 nsIContent
& thisContent
= AsContent();
230 if (thisContent
.IsInSVGUseShadowTree()) {
231 // Stylesheets in <use>-cloned subtrees are disabled until we figure out
232 // how they should behave.
236 if (mStyleSheet
&& (aOldDocument
|| aOldShadowRoot
)) {
237 MOZ_ASSERT(!(aOldDocument
&& aOldShadowRoot
),
238 "ShadowRoot content is never in document, thus "
239 "there should not be a old document and old "
240 "ShadowRoot simultaneously.");
242 // We're removing the link element from the document or shadow tree, unload
245 // We want to do this even if updates are disabled, since otherwise a sheet
246 // with a stale linking element pointer will be hanging around -- not good!
247 if (mStyleSheet
->IsComplete()) {
248 if (aOldShadowRoot
) {
249 aOldShadowRoot
->RemoveStyleSheet(*mStyleSheet
);
251 aOldDocument
->RemoveStyleSheet(*mStyleSheet
);
255 SetStyleSheet(nullptr);
258 Document
* doc
= thisContent
.GetComposedDoc();
260 // Loader could be null during unlink, see bug 1425866.
261 if (!doc
|| !doc
->CSSLoader() || !doc
->CSSLoader()->GetEnabled()) {
265 // When static documents are created, stylesheets are cloned manually.
266 if (!mUpdatesEnabled
|| doc
->IsStaticDocument()) {
270 Maybe
<SheetInfo
> info
= GetStyleSheetInfo();
271 if (aForceUpdate
== ForceUpdate::No
&& mStyleSheet
&& info
&&
272 !info
->mIsInline
&& info
->mURI
) {
273 if (nsIURI
* oldURI
= mStyleSheet
->GetSheetURI()) {
275 nsresult rv
= oldURI
->Equals(info
->mURI
, &equal
);
276 if (NS_SUCCEEDED(rv
) && equal
) {
283 if (mStyleSheet
->IsComplete()) {
284 if (thisContent
.IsInShadowTree()) {
285 ShadowRoot
* containingShadow
= thisContent
.GetContainingShadow();
286 // Could be null only during unlink.
287 if (MOZ_LIKELY(containingShadow
)) {
288 containingShadow
->RemoveStyleSheet(*mStyleSheet
);
291 doc
->RemoveStyleSheet(*mStyleSheet
);
295 SetStyleSheet(nullptr);
302 if (!info
->mURI
&& !info
->mIsInline
) {
303 // If href is empty and this is not inline style then just bail
307 if (info
->mIsInline
) {
309 if (!nsContentUtils::GetNodeTextContent(&thisContent
, false, text
,
311 return Err(NS_ERROR_OUT_OF_MEMORY
);
314 MOZ_ASSERT(thisContent
.NodeInfo()->NameAtom() != nsGkAtoms::link
,
315 "<link> is not 'inline', and needs different CSP checks");
316 MOZ_ASSERT(thisContent
.IsElement());
318 if (!nsStyleUtil::CSPAllowsInlineStyle(
319 thisContent
.AsElement(), doc
, info
->mTriggeringPrincipal
,
320 mLineNumber
, mColumnNumber
, text
, &rv
)) {
327 // Parse the style sheet.
328 return doc
->CSSLoader()->LoadInlineStyle(*info
, text
, aObserver
);
330 if (thisContent
.IsElement()) {
331 nsAutoString integrity
;
332 thisContent
.AsElement()->GetAttr(nsGkAtoms::integrity
, integrity
);
333 if (!integrity
.IsEmpty()) {
334 MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug
,
335 ("LinkStyle::DoUpdateStyleSheet, integrity=%s",
336 NS_ConvertUTF16toUTF8(integrity
).get()));
339 auto resultOrError
= doc
->CSSLoader()->LoadStyleLink(*info
, aObserver
);
340 if (resultOrError
.isErr()) {
341 // Don't propagate LoadStyleLink() errors further than this, since some
342 // consumers (e.g. nsXMLContentSink) will completely abort on innocuous
343 // things like a stylesheet load being blocked by the security system.
346 return resultOrError
;
349 } // namespace mozilla::dom