/src/mozilla-central/dom/base/nsStyleLinkElement.cpp
Line | Count | Source (jump to first uncovered line) |
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 "nsUnicharUtils.h" |
27 | | #include "nsCRT.h" |
28 | | #include "nsXPCOMCIDInternal.h" |
29 | | #include "nsUnicharInputStream.h" |
30 | | #include "nsContentUtils.h" |
31 | | #include "nsStyleUtil.h" |
32 | | #include "nsQueryObject.h" |
33 | | |
34 | | using namespace mozilla; |
35 | | using namespace mozilla::dom; |
36 | | |
37 | | nsStyleLinkElement::SheetInfo::SheetInfo( |
38 | | const nsIDocument& aDocument, |
39 | | nsIContent* aContent, |
40 | | already_AddRefed<nsIURI> aURI, |
41 | | already_AddRefed<nsIPrincipal> aTriggeringPrincipal, |
42 | | mozilla::net::ReferrerPolicy aReferrerPolicy, |
43 | | mozilla::CORSMode aCORSMode, |
44 | | const nsAString& aTitle, |
45 | | const nsAString& aMedia, |
46 | | HasAlternateRel aHasAlternateRel, |
47 | | IsInline aIsInline |
48 | | ) |
49 | | : mContent(aContent) |
50 | | , mURI(aURI) |
51 | | , mTriggeringPrincipal(aTriggeringPrincipal) |
52 | | , mReferrerPolicy(aReferrerPolicy) |
53 | | , mCORSMode(aCORSMode) |
54 | | , mTitle(aTitle) |
55 | | , mMedia(aMedia) |
56 | | , mHasAlternateRel(aHasAlternateRel == HasAlternateRel::Yes) |
57 | | , mIsInline(aIsInline == IsInline::Yes) |
58 | 0 | { |
59 | 0 | MOZ_ASSERT(!mIsInline || aContent); |
60 | 0 | MOZ_ASSERT_IF(aContent, aContent->OwnerDoc() == &aDocument); |
61 | 0 |
|
62 | 0 | if (mReferrerPolicy == net::ReferrerPolicy::RP_Unset) { |
63 | 0 | mReferrerPolicy = aDocument.GetReferrerPolicy(); |
64 | 0 | } |
65 | 0 |
|
66 | 0 | if (!mIsInline && aContent && aContent->IsElement()) { |
67 | 0 | aContent->AsElement()->GetAttr(kNameSpaceID_None, |
68 | 0 | nsGkAtoms::integrity, |
69 | 0 | mIntegrity); |
70 | 0 | } |
71 | 0 | } |
72 | | |
73 | 0 | nsStyleLinkElement::SheetInfo::~SheetInfo() = default; |
74 | | |
75 | | nsStyleLinkElement::nsStyleLinkElement() |
76 | | : mDontLoadStyle(false) |
77 | | , mUpdatesEnabled(true) |
78 | | , mLineNumber(1) |
79 | | , mColumnNumber(1) |
80 | 0 | { |
81 | 0 | } |
82 | | |
83 | | nsStyleLinkElement::~nsStyleLinkElement() |
84 | 0 | { |
85 | 0 | nsStyleLinkElement::SetStyleSheet(nullptr); |
86 | 0 | } |
87 | | |
88 | | void |
89 | | nsStyleLinkElement::GetTitleAndMediaForElement(const Element& aSelf, |
90 | | nsString& aTitle, |
91 | | nsString& aMedia) |
92 | 0 | { |
93 | 0 | // Only honor title as stylesheet name for elements in the document (that is, |
94 | 0 | // ignore for Shadow DOM), per [1] and [2]. See [3]. |
95 | 0 | // |
96 | 0 | // [1]: https://html.spec.whatwg.org/#attr-link-title |
97 | 0 | // [2]: https://html.spec.whatwg.org/#attr-style-title |
98 | 0 | // [3]: https://github.com/w3c/webcomponents/issues/535 |
99 | 0 | if (aSelf.IsInUncomposedDoc()) { |
100 | 0 | aSelf.GetAttr(kNameSpaceID_None, nsGkAtoms::title, aTitle); |
101 | 0 | aTitle.CompressWhitespace(); |
102 | 0 | } |
103 | 0 |
|
104 | 0 | aSelf.GetAttr(kNameSpaceID_None, nsGkAtoms::media, aMedia); |
105 | 0 | // The HTML5 spec is formulated in terms of the CSSOM spec, which specifies |
106 | 0 | // that media queries should be ASCII lowercased during serialization. |
107 | 0 | // |
108 | 0 | // FIXME(emilio): How does it matter? This is going to be parsed anyway, CSS |
109 | 0 | // should take care of serializing it properly. |
110 | 0 | nsContentUtils::ASCIIToLower(aMedia); |
111 | 0 | } |
112 | | |
113 | | bool |
114 | | nsStyleLinkElement::IsCSSMimeTypeAttribute(const Element& aSelf) |
115 | 0 | { |
116 | 0 | nsAutoString type; |
117 | 0 | nsAutoString mimeType; |
118 | 0 | nsAutoString notUsed; |
119 | 0 | aSelf.GetAttr(kNameSpaceID_None, nsGkAtoms::type, type); |
120 | 0 | nsContentUtils::SplitMimeType(type, mimeType, notUsed); |
121 | 0 | return mimeType.IsEmpty() || mimeType.LowerCaseEqualsLiteral("text/css"); |
122 | 0 | } |
123 | | |
124 | | void |
125 | | nsStyleLinkElement::Unlink() |
126 | 0 | { |
127 | 0 | nsStyleLinkElement::SetStyleSheet(nullptr); |
128 | 0 | } |
129 | | |
130 | | void |
131 | | nsStyleLinkElement::Traverse(nsCycleCollectionTraversalCallback &cb) |
132 | 0 | { |
133 | 0 | nsStyleLinkElement* tmp = this; |
134 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheet); |
135 | 0 | } |
136 | | |
137 | | void |
138 | | nsStyleLinkElement::SetStyleSheet(StyleSheet* aStyleSheet) |
139 | 0 | { |
140 | 0 | if (mStyleSheet) { |
141 | 0 | mStyleSheet->SetOwningNode(nullptr); |
142 | 0 | } |
143 | 0 |
|
144 | 0 | mStyleSheet = aStyleSheet; |
145 | 0 | if (mStyleSheet) { |
146 | 0 | nsCOMPtr<nsINode> node = do_QueryObject(this); |
147 | 0 | if (node) { |
148 | 0 | mStyleSheet->SetOwningNode(node); |
149 | 0 | } |
150 | 0 | } |
151 | 0 | } |
152 | | |
153 | | StyleSheet* |
154 | | nsStyleLinkElement::GetStyleSheet() |
155 | 0 | { |
156 | 0 | return mStyleSheet; |
157 | 0 | } |
158 | | |
159 | | void |
160 | | nsStyleLinkElement::InitStyleLinkElement(bool aDontLoadStyle) |
161 | 0 | { |
162 | 0 | mDontLoadStyle = aDontLoadStyle; |
163 | 0 | } |
164 | | |
165 | | void |
166 | | nsStyleLinkElement::SetEnableUpdates(bool aEnableUpdates) |
167 | 0 | { |
168 | 0 | mUpdatesEnabled = aEnableUpdates; |
169 | 0 | } |
170 | | |
171 | | void |
172 | | nsStyleLinkElement::GetCharset(nsAString& aCharset) |
173 | 0 | { |
174 | 0 | aCharset.Truncate(); |
175 | 0 | } |
176 | | |
177 | | /* virtual */ void |
178 | | nsStyleLinkElement::OverrideBaseURI(nsIURI* aNewBaseURI) |
179 | 0 | { |
180 | 0 | MOZ_ASSERT_UNREACHABLE("Base URI can't be overriden in this implementation " |
181 | 0 | "of nsIStyleSheetLinkingElement."); |
182 | 0 | } |
183 | | |
184 | | /* virtual */ void |
185 | | nsStyleLinkElement::SetLineNumber(uint32_t aLineNumber) |
186 | 0 | { |
187 | 0 | mLineNumber = aLineNumber; |
188 | 0 | } |
189 | | |
190 | | /* virtual */ uint32_t |
191 | | nsStyleLinkElement::GetLineNumber() |
192 | 0 | { |
193 | 0 | return mLineNumber; |
194 | 0 | } |
195 | | |
196 | | /* virtual */ void |
197 | | nsStyleLinkElement::SetColumnNumber(uint32_t aColumnNumber) |
198 | 0 | { |
199 | 0 | mColumnNumber = aColumnNumber; |
200 | 0 | } |
201 | | |
202 | | /* virtual */ uint32_t |
203 | | nsStyleLinkElement::GetColumnNumber() |
204 | 0 | { |
205 | 0 | return mColumnNumber; |
206 | 0 | } |
207 | | |
208 | | static uint32_t ToLinkMask(const nsAString& aLink) |
209 | 0 | { |
210 | 0 | // Keep this in sync with sRelValues in HTMLLinkElement.cpp |
211 | 0 | if (aLink.EqualsLiteral("prefetch")) |
212 | 0 | return nsStyleLinkElement::ePREFETCH; |
213 | 0 | else if (aLink.EqualsLiteral("dns-prefetch")) |
214 | 0 | return nsStyleLinkElement::eDNS_PREFETCH; |
215 | 0 | else if (aLink.EqualsLiteral("stylesheet")) |
216 | 0 | return nsStyleLinkElement::eSTYLESHEET; |
217 | 0 | else if (aLink.EqualsLiteral("next")) |
218 | 0 | return nsStyleLinkElement::eNEXT; |
219 | 0 | else if (aLink.EqualsLiteral("alternate")) |
220 | 0 | return nsStyleLinkElement::eALTERNATE; |
221 | 0 | else if (aLink.EqualsLiteral("preconnect")) |
222 | 0 | return nsStyleLinkElement::ePRECONNECT; |
223 | 0 | else if (aLink.EqualsLiteral("preload")) |
224 | 0 | return nsStyleLinkElement::ePRELOAD; |
225 | 0 | else |
226 | 0 | return 0; |
227 | 0 | } |
228 | | |
229 | | uint32_t nsStyleLinkElement::ParseLinkTypes(const nsAString& aTypes) |
230 | 0 | { |
231 | 0 | uint32_t linkMask = 0; |
232 | 0 | nsAString::const_iterator start, done; |
233 | 0 | aTypes.BeginReading(start); |
234 | 0 | aTypes.EndReading(done); |
235 | 0 | if (start == done) |
236 | 0 | return linkMask; |
237 | 0 | |
238 | 0 | nsAString::const_iterator current(start); |
239 | 0 | bool inString = !nsContentUtils::IsHTMLWhitespace(*current); |
240 | 0 | nsAutoString subString; |
241 | 0 |
|
242 | 0 | while (current != done) { |
243 | 0 | if (nsContentUtils::IsHTMLWhitespace(*current)) { |
244 | 0 | if (inString) { |
245 | 0 | nsContentUtils::ASCIIToLower(Substring(start, current), subString); |
246 | 0 | linkMask |= ToLinkMask(subString); |
247 | 0 | inString = false; |
248 | 0 | } |
249 | 0 | } |
250 | 0 | else { |
251 | 0 | if (!inString) { |
252 | 0 | start = current; |
253 | 0 | inString = true; |
254 | 0 | } |
255 | 0 | } |
256 | 0 | ++current; |
257 | 0 | } |
258 | 0 | if (inString) { |
259 | 0 | nsContentUtils::ASCIIToLower(Substring(start, current), subString); |
260 | 0 | linkMask |= ToLinkMask(subString); |
261 | 0 | } |
262 | 0 | return linkMask; |
263 | 0 | } |
264 | | |
265 | | Result<nsStyleLinkElement::Update, nsresult> |
266 | | nsStyleLinkElement::UpdateStyleSheet(nsICSSLoaderObserver* aObserver) |
267 | 0 | { |
268 | 0 | return DoUpdateStyleSheet(nullptr, nullptr, aObserver, ForceUpdate::No); |
269 | 0 | } |
270 | | |
271 | | Result<nsStyleLinkElement::Update, nsresult> |
272 | | nsStyleLinkElement::UpdateStyleSheetInternal(nsIDocument* aOldDocument, |
273 | | ShadowRoot* aOldShadowRoot, |
274 | | ForceUpdate aForceUpdate) |
275 | 0 | { |
276 | 0 | return DoUpdateStyleSheet( |
277 | 0 | aOldDocument, aOldShadowRoot, nullptr, aForceUpdate); |
278 | 0 | } |
279 | | |
280 | | Result<nsStyleLinkElement::Update, nsresult> |
281 | | nsStyleLinkElement::DoUpdateStyleSheet(nsIDocument* aOldDocument, |
282 | | ShadowRoot* aOldShadowRoot, |
283 | | nsICSSLoaderObserver* aObserver, |
284 | | ForceUpdate aForceUpdate) |
285 | 0 | { |
286 | 0 | nsCOMPtr<nsIContent> thisContent = do_QueryInterface(this); |
287 | 0 | // All instances of nsStyleLinkElement should implement nsIContent. |
288 | 0 | MOZ_ASSERT(thisContent); |
289 | 0 |
|
290 | 0 | if (thisContent->IsInSVGUseShadowTree()) { |
291 | 0 | // Stylesheets in <use>-cloned subtrees are disabled until we figure out |
292 | 0 | // how they should behave. |
293 | 0 | return Update { }; |
294 | 0 | } |
295 | 0 | |
296 | 0 | if (mStyleSheet && (aOldDocument || aOldShadowRoot)) { |
297 | 0 | MOZ_ASSERT(!(aOldDocument && aOldShadowRoot), |
298 | 0 | "ShadowRoot content is never in document, thus " |
299 | 0 | "there should not be a old document and old " |
300 | 0 | "ShadowRoot simultaneously."); |
301 | 0 |
|
302 | 0 | // We're removing the link element from the document or shadow tree, |
303 | 0 | // unload the stylesheet. We want to do this even if updates are |
304 | 0 | // disabled, since otherwise a sheet with a stale linking element pointer |
305 | 0 | // will be hanging around -- not good! |
306 | 0 | if (aOldShadowRoot) { |
307 | 0 | aOldShadowRoot->RemoveSheet(mStyleSheet); |
308 | 0 | } else { |
309 | 0 | aOldDocument->RemoveStyleSheet(mStyleSheet); |
310 | 0 | } |
311 | 0 |
|
312 | 0 | SetStyleSheet(nullptr); |
313 | 0 | } |
314 | 0 |
|
315 | 0 | nsIDocument* doc = thisContent->GetComposedDoc(); |
316 | 0 |
|
317 | 0 | // Loader could be null during unlink, see bug 1425866. |
318 | 0 | if (!doc || !doc->CSSLoader() || !doc->CSSLoader()->GetEnabled()) { |
319 | 0 | return Update { }; |
320 | 0 | } |
321 | 0 | |
322 | 0 | // When static documents are created, stylesheets are cloned manually. |
323 | 0 | if (mDontLoadStyle || !mUpdatesEnabled || doc->IsStaticDocument()) { |
324 | 0 | return Update { }; |
325 | 0 | } |
326 | 0 | |
327 | 0 | Maybe<SheetInfo> info = GetStyleSheetInfo(); |
328 | 0 | if (aForceUpdate == ForceUpdate::No && |
329 | 0 | mStyleSheet && |
330 | 0 | info && |
331 | 0 | !info->mIsInline && |
332 | 0 | info->mURI) { |
333 | 0 | if (nsIURI* oldURI = mStyleSheet->GetSheetURI()) { |
334 | 0 | bool equal; |
335 | 0 | nsresult rv = oldURI->Equals(info->mURI, &equal); |
336 | 0 | if (NS_SUCCEEDED(rv) && equal) { |
337 | 0 | return Update { }; |
338 | 0 | } |
339 | 0 | } |
340 | 0 | } |
341 | 0 | |
342 | 0 | if (mStyleSheet) { |
343 | 0 | if (thisContent->IsInShadowTree()) { |
344 | 0 | ShadowRoot* containingShadow = thisContent->GetContainingShadow(); |
345 | 0 | // Could be null only during unlink. |
346 | 0 | if (MOZ_LIKELY(containingShadow)) { |
347 | 0 | containingShadow->RemoveSheet(mStyleSheet); |
348 | 0 | } |
349 | 0 | } else { |
350 | 0 | doc->RemoveStyleSheet(mStyleSheet); |
351 | 0 | } |
352 | 0 |
|
353 | 0 | nsStyleLinkElement::SetStyleSheet(nullptr); |
354 | 0 | } |
355 | 0 |
|
356 | 0 | if (!info) { |
357 | 0 | return Update { }; |
358 | 0 | } |
359 | 0 | |
360 | 0 | MOZ_ASSERT(info->mReferrerPolicy != net::RP_Unset || |
361 | 0 | info->mReferrerPolicy == doc->GetReferrerPolicy()); |
362 | 0 | if (!info->mURI && !info->mIsInline) { |
363 | 0 | // If href is empty and this is not inline style then just bail |
364 | 0 | return Update { }; |
365 | 0 | } |
366 | 0 | |
367 | 0 | if (info->mIsInline) { |
368 | 0 | nsAutoString text; |
369 | 0 | if (!nsContentUtils::GetNodeTextContent(thisContent, false, text, fallible)) { |
370 | 0 | return Err(NS_ERROR_OUT_OF_MEMORY); |
371 | 0 | } |
372 | 0 | |
373 | 0 | |
374 | 0 | MOZ_ASSERT(thisContent->NodeInfo()->NameAtom() != nsGkAtoms::link, |
375 | 0 | "<link> is not 'inline', and needs different CSP checks"); |
376 | 0 | MOZ_ASSERT(thisContent->IsElement()); |
377 | 0 | nsresult rv = NS_OK; |
378 | 0 | if (!nsStyleUtil::CSPAllowsInlineStyle(thisContent->AsElement(), |
379 | 0 | thisContent->NodePrincipal(), |
380 | 0 | info->mTriggeringPrincipal, |
381 | 0 | doc->GetDocumentURI(), |
382 | 0 | mLineNumber, mColumnNumber, text, |
383 | 0 | &rv)) { |
384 | 0 | if (NS_FAILED(rv)) { |
385 | 0 | return Err(rv); |
386 | 0 | } |
387 | 0 | return Update { }; |
388 | 0 | } |
389 | 0 | |
390 | 0 | // Parse the style sheet. |
391 | 0 | return doc->CSSLoader()->LoadInlineStyle(*info, text, mLineNumber, aObserver); |
392 | 0 | } |
393 | 0 | if (thisContent->IsElement()) { |
394 | 0 | nsAutoString integrity; |
395 | 0 | thisContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, |
396 | 0 | integrity); |
397 | 0 | if (!integrity.IsEmpty()) { |
398 | 0 | MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug, |
399 | 0 | ("nsStyleLinkElement::DoUpdateStyleSheet, integrity=%s", |
400 | 0 | NS_ConvertUTF16toUTF8(integrity).get())); |
401 | 0 | } |
402 | 0 | } |
403 | 0 | auto resultOrError = doc->CSSLoader()->LoadStyleLink(*info, aObserver); |
404 | 0 | if (resultOrError.isErr()) { |
405 | 0 | // Don't propagate LoadStyleLink() errors further than this, since some |
406 | 0 | // consumers (e.g. nsXMLContentSink) will completely abort on innocuous |
407 | 0 | // things like a stylesheet load being blocked by the security system. |
408 | 0 | return Update { }; |
409 | 0 | } |
410 | 0 | return resultOrError; |
411 | 0 | } |