LinkStyle.cpp (13355B)
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 "mozilla/dom/LinkStyle.h" 14 15 #include "mozilla/Preferences.h" 16 #include "mozilla/StaticPrefs_dom.h" 17 #include "mozilla/StyleSheet.h" 18 #include "mozilla/StyleSheetInlines.h" 19 #include "mozilla/css/Loader.h" 20 #include "mozilla/dom/Document.h" 21 #include "mozilla/dom/Element.h" 22 #include "mozilla/dom/FragmentOrElement.h" 23 #include "mozilla/dom/HTMLLinkElement.h" 24 #include "mozilla/dom/HTMLStyleElement.h" 25 #include "mozilla/dom/SRILogHelper.h" 26 #include "mozilla/dom/SVGStyleElement.h" 27 #include "mozilla/dom/ShadowRoot.h" 28 #include "nsCRT.h" 29 #include "nsContentUtils.h" 30 #include "nsIContent.h" 31 #include "nsQueryObject.h" 32 #include "nsStyleUtil.h" 33 #include "nsUnicharInputStream.h" 34 #include "nsUnicharUtils.h" 35 #include "nsXPCOMCIDInternal.h" 36 37 namespace mozilla::dom { 38 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) 49 : mContent(aContent), 50 mURI(aURI), 51 mTriggeringPrincipal(aTriggeringPrincipal), 52 mReferrerInfo(aReferrerInfo), 53 mCORSMode(aCORSMode), 54 mTitle(aTitle), 55 mMedia(aMedia), 56 mIntegrity(aIntegrity), 57 mNonce(aNonce), 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>"); 67 } 68 69 LinkStyle::SheetInfo::~SheetInfo() = default; 70 LinkStyle::LinkStyle() = default; 71 72 LinkStyle::~LinkStyle() { LinkStyle::SetStyleSheet(nullptr); } 73 74 StyleSheet* LinkStyle::GetSheetForBindings() const { 75 if (mStyleSheet && mStyleSheet->IsComplete()) { 76 return mStyleSheet; 77 } 78 return nullptr; 79 } 80 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]. 85 // 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(); 92 } 93 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. 97 // 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); 101 } 102 103 bool LinkStyle::IsCSSMimeTypeAttributeForStyleElement(const Element& aSelf) { 104 // Per 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 107 // attribute values. 108 nsAutoString type; 109 aSelf.GetAttr(nsGkAtoms::type, type); 110 return type.IsEmpty() || type.LowerCaseEqualsLiteral("text/css"); 111 } 112 113 void LinkStyle::Unlink() { LinkStyle::SetStyleSheet(nullptr); } 114 115 void LinkStyle::Traverse(nsCycleCollectionTraversalCallback& cb) { 116 LinkStyle* tmp = this; 117 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheet); 118 } 119 120 void LinkStyle::SetStyleSheet(StyleSheet* aStyleSheet) { 121 if (mStyleSheet) { 122 mStyleSheet->SetOwningNode(nullptr); 123 } 124 125 mStyleSheet = aStyleSheet; 126 if (mStyleSheet) { 127 mStyleSheet->SetOwningNode(&AsContent()); 128 } 129 } 130 131 void LinkStyle::GetCharset(nsAString& aCharset) { aCharset.Truncate(); } 132 133 static uint32_t ToLinkMask(const nsAString& aLink) { 134 // Keep this in sync with sSupportedRelValues in HTMLLinkElement.cpp 135 uint32_t mask = 0; 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; 152 } else if (aLink.EqualsLiteral("compression-dictionary")) { 153 mask = LinkStyle::eCOMPRESSION_DICTIONARY; 154 } 155 156 return mask; 157 } 158 159 uint32_t LinkStyle::ParseLinkTypes(const nsAString& aTypes) { 160 uint32_t linkMask = 0; 161 nsAString::const_iterator start, done; 162 aTypes.BeginReading(start); 163 aTypes.EndReading(done); 164 if (start == done) return linkMask; 165 166 nsAString::const_iterator current(start); 167 bool inString = !nsContentUtils::IsHTMLWhitespace(*current); 168 nsAutoString subString; 169 170 while (current != done) { 171 if (nsContentUtils::IsHTMLWhitespace(*current)) { 172 if (inString) { 173 nsContentUtils::ASCIIToLower(Substring(start, current), subString); 174 linkMask |= ToLinkMask(subString); 175 inString = false; 176 } 177 } else { 178 if (!inString) { 179 start = current; 180 inString = true; 181 } 182 } 183 ++current; 184 } 185 if (inString) { 186 nsContentUtils::ASCIIToLower(Substring(start, current), subString); 187 linkMask |= ToLinkMask(subString); 188 } 189 return linkMask; 190 } 191 192 Result<LinkStyle::Update, nsresult> LinkStyle::UpdateStyleSheetInternal( 193 Document* aOldDocument, ShadowRoot* aOldShadowRoot, 194 ForceUpdate aForceUpdate) { 195 return DoUpdateStyleSheet(aOldDocument, aOldShadowRoot, nullptr, 196 aForceUpdate); 197 } 198 199 LinkStyle* LinkStyle::FromNode(Element& aElement) { 200 nsAtom* name = aElement.NodeInfo()->NameAtom(); 201 if (name == nsGkAtoms::link) { 202 MOZ_ASSERT(aElement.IsHTMLElement() == !!aElement.AsLinkStyle()); 203 return aElement.IsHTMLElement() ? static_cast<HTMLLinkElement*>(&aElement) 204 : nullptr; 205 } 206 if (name == nsGkAtoms::style) { 207 if (aElement.IsHTMLElement()) { 208 MOZ_ASSERT(aElement.AsLinkStyle()); 209 return static_cast<HTMLStyleElement*>(&aElement); 210 } 211 if (aElement.IsSVGElement()) { 212 MOZ_ASSERT(aElement.AsLinkStyle()); 213 return static_cast<SVGStyleElement*>(&aElement); 214 } 215 } 216 MOZ_ASSERT(!aElement.AsLinkStyle()); 217 return nullptr; 218 } 219 220 void LinkStyle::BindToTree() { 221 if (mUpdatesEnabled) { 222 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( 223 "LinkStyle::BindToTree", 224 [this, pin = RefPtr{&AsContent()}] { UpdateStyleSheetInternal(); })); 225 } 226 } 227 228 Result<LinkStyle::Update, nsresult> LinkStyle::DoUpdateStyleSheet( 229 Document* aOldDocument, ShadowRoot* aOldShadowRoot, 230 nsICSSLoaderObserver* aObserver, ForceUpdate aForceUpdate) { 231 nsIContent& thisContent = AsContent(); 232 if (thisContent.IsInSVGUseShadowTree()) { 233 // Stylesheets in <use>-cloned subtrees are disabled until we figure out 234 // how they should behave. 235 return Update{}; 236 } 237 238 if (mStyleSheet && (aOldDocument || aOldShadowRoot)) { 239 MOZ_ASSERT(!(aOldDocument && aOldShadowRoot), 240 "ShadowRoot content is never in document, thus " 241 "there should not be a old document and old " 242 "ShadowRoot simultaneously."); 243 244 // We're removing the link element from the document or shadow tree, unload 245 // the stylesheet. 246 // 247 // We want to do this even if updates are disabled, since otherwise a sheet 248 // with a stale linking element pointer will be hanging around -- not good! 249 if (mStyleSheet->IsComplete()) { 250 if (aOldShadowRoot) { 251 aOldShadowRoot->RemoveStyleSheet(*mStyleSheet); 252 } else { 253 aOldDocument->RemoveStyleSheet(*mStyleSheet); 254 } 255 } 256 257 SetStyleSheet(nullptr); 258 } 259 260 Document* doc = thisContent.GetComposedDoc(); 261 262 // Loader could be null during unlink, see bug 1425866. 263 // ... No need to update if updating is disabled, as well. 264 if (!doc || !doc->EnsureCSSLoader().GetEnabled() || !mUpdatesEnabled) { 265 return Update{}; 266 } 267 268 // When static documents are created, we need to finish up cloning 269 // the stylesheet (See documentation for MaybeFinishCopyStyleSheet). 270 if (doc->IsStaticDocument()) { 271 MaybeFinishCopyStyleSheet(doc); 272 return Update{}; 273 } 274 275 Maybe<SheetInfo> info = GetStyleSheetInfo(); 276 if (aForceUpdate == ForceUpdate::No && mStyleSheet && info && 277 !info->mIsInline && info->mURI) { 278 if (nsIURI* oldURI = mStyleSheet->GetOriginalURI()) { 279 bool equal; 280 nsresult rv = oldURI->Equals(info->mURI, &equal); 281 if (NS_SUCCEEDED(rv) && equal) { 282 return Update{}; 283 } 284 } 285 } 286 287 if (mStyleSheet) { 288 if (mStyleSheet->IsComplete()) { 289 if (thisContent.IsInShadowTree()) { 290 ShadowRoot* containingShadow = thisContent.GetContainingShadow(); 291 // Could be null only during unlink. 292 if (MOZ_LIKELY(containingShadow)) { 293 containingShadow->RemoveStyleSheet(*mStyleSheet); 294 } 295 } else { 296 doc->RemoveStyleSheet(*mStyleSheet); 297 } 298 } 299 300 SetStyleSheet(nullptr); 301 } 302 303 if (!info) { 304 return Update{}; 305 } 306 307 if (!info->mURI && !info->mIsInline) { 308 // If href is empty and this is not inline style then just bail 309 return Update{}; 310 } 311 312 if (info->mIsInline) { 313 nsAutoString text; 314 if (!nsContentUtils::GetNodeTextContent(&thisContent, false, text, 315 fallible)) { 316 return Err(NS_ERROR_OUT_OF_MEMORY); 317 } 318 319 MOZ_ASSERT(thisContent.NodeInfo()->NameAtom() != nsGkAtoms::link, 320 "<link> is not 'inline', and needs different CSP checks"); 321 MOZ_ASSERT(thisContent.IsElement()); 322 nsresult rv = NS_OK; 323 if (!nsStyleUtil::CSPAllowsInlineStyle( 324 thisContent.AsElement(), doc, info->mTriggeringPrincipal, 325 mLineNumber, mColumnNumber, text, &rv)) { 326 if (NS_FAILED(rv)) { 327 return Err(rv); 328 } 329 return Update{}; 330 } 331 332 // Parse the style sheet. 333 return doc->EnsureCSSLoader().LoadInlineStyle(*info, text, aObserver); 334 } 335 if (thisContent.IsElement()) { 336 nsAutoString integrity; 337 thisContent.AsElement()->GetAttr(nsGkAtoms::integrity, integrity); 338 if (!integrity.IsEmpty()) { 339 MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug, 340 ("LinkStyle::DoUpdateStyleSheet, integrity=%s", 341 NS_ConvertUTF16toUTF8(integrity).get())); 342 } 343 } 344 auto resultOrError = doc->EnsureCSSLoader().LoadStyleLink(*info, aObserver); 345 if (resultOrError.isErr()) { 346 // Don't propagate LoadStyleLink() errors further than this, since some 347 // consumers (e.g. nsXMLContentSink) will completely abort on innocuous 348 // things like a stylesheet load being blocked by the security system. 349 return Update{}; 350 } 351 return resultOrError; 352 } 353 354 void LinkStyle::MaybeStartCopyStyleSheetTo(LinkStyle* aDest, 355 Document* aDoc) const { 356 MOZ_ASSERT(aDoc, "Copying to null Document?"); 357 if (!aDoc->IsStaticDocument() || !mStyleSheet || 358 !mStyleSheet->IsApplicable()) { 359 return; 360 } 361 362 // We don't yet if know we're in shadow root, so the only thing we can do is 363 // to keep an incomplete clone. Namely, the sheet does not have knowledge of 364 // its owning node and which document or shadow root it belongs to. 365 aDest->mStyleSheet = mStyleSheet->Clone(nullptr, nullptr); 366 } 367 368 void LinkStyle::MaybeFinishCopyStyleSheet(Document* aDocument) { 369 if (!mStyleSheet) { 370 return; 371 } 372 auto& thisContent = AsContent(); 373 // Are we in the holdover copy state? 374 if (mStyleSheet->GetOwnerNode() == &thisContent) { 375 return; 376 } 377 MOZ_ASSERT(aDocument->IsStaticDocument(), 378 "Copying stylesheet over into a non-static document?"); 379 380 DocumentOrShadowRoot* root = aDocument; 381 auto* shadowRoot = thisContent.GetContainingShadow(); 382 if (shadowRoot) { 383 root = shadowRoot; 384 if (MOZ_UNLIKELY(!root)) { 385 // This can happen during unlink - just drop the holdover stylesheet. 386 mStyleSheet = nullptr; 387 return; 388 } 389 } 390 RefPtr<StyleSheet> sheet = mStyleSheet->Clone(nullptr, root); 391 SetStyleSheet(sheet.get()); 392 aDocument->EnsureCSSLoader().InsertSheetInTree(*sheet); 393 } 394 395 } // namespace mozilla::dom