HTMLLinkElement.cpp (25311B)
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 #include "mozilla/dom/HTMLLinkElement.h" 8 9 #include "DecoderDoctorDiagnostics.h" 10 #include "DecoderTraits.h" 11 #include "MediaContainerType.h" 12 #include "MediaList.h" 13 #include "imgLoader.h" 14 #include "mozilla/AsyncEventDispatcher.h" 15 #include "mozilla/Components.h" 16 #include "mozilla/EventDispatcher.h" 17 #include "mozilla/Preferences.h" 18 #include "mozilla/StaticPrefs_dom.h" 19 #include "mozilla/StaticPrefs_network.h" 20 #include "mozilla/dom/BindContext.h" 21 #include "mozilla/dom/Document.h" 22 #include "mozilla/dom/DocumentInlines.h" 23 #include "mozilla/dom/HTMLDNSPrefetch.h" 24 #include "mozilla/dom/HTMLLinkElementBinding.h" 25 #include "mozilla/dom/ReferrerInfo.h" 26 #include "mozilla/dom/ScriptLoader.h" 27 #include "nsAttrValueInlines.h" 28 #include "nsAttrValueOrString.h" 29 #include "nsContentUtils.h" 30 #include "nsDOMTokenList.h" 31 #include "nsGenericHTMLElement.h" 32 #include "nsGkAtoms.h" 33 #include "nsIContentInlines.h" 34 #include "nsIContentPolicy.h" 35 #include "nsINode.h" 36 #include "nsIPrefetchService.h" 37 #include "nsMimeTypes.h" 38 #include "nsPIDOMWindow.h" 39 #include "nsReadableUtils.h" 40 #include "nsStyleConsts.h" 41 #include "nsUnicharUtils.h" 42 #include "nsWindowSizes.h" 43 44 NS_IMPL_NS_NEW_HTML_ELEMENT(Link) 45 46 namespace mozilla::dom { 47 48 HTMLLinkElement::HTMLLinkElement( 49 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) 50 : nsGenericHTMLElement(std::move(aNodeInfo)) {} 51 52 HTMLLinkElement::~HTMLLinkElement() { SupportsDNSPrefetch::Destroyed(*this); } 53 54 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLLinkElement) 55 56 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLLinkElement, 57 nsGenericHTMLElement) 58 tmp->LinkStyle::Traverse(cb); 59 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelList) 60 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSizes) 61 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBlocking) 62 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 63 64 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLLinkElement, 65 nsGenericHTMLElement) 66 tmp->LinkStyle::Unlink(); 67 NS_IMPL_CYCLE_COLLECTION_UNLINK(mRelList) 68 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSizes) 69 NS_IMPL_CYCLE_COLLECTION_UNLINK(mBlocking) 70 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 71 72 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLLinkElement, 73 nsGenericHTMLElement) 74 75 NS_IMPL_ELEMENT_CLONE(HTMLLinkElement) 76 77 bool HTMLLinkElement::Disabled() const { 78 return GetBoolAttr(nsGkAtoms::disabled); 79 } 80 81 void HTMLLinkElement::SetDisabled(bool aDisabled, ErrorResult& aRv) { 82 return SetHTMLBoolAttr(nsGkAtoms::disabled, aDisabled, aRv); 83 } 84 85 nsresult HTMLLinkElement::BindToTree(BindContext& aContext, nsINode& aParent) { 86 nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent); 87 NS_ENSURE_SUCCESS(rv, rv); 88 89 if (IsInComposedDoc()) { 90 TryDNSPrefetchOrPreconnectOrPrefetchOrPreloadOrPrerender(); 91 } 92 93 LinkStyle::BindToTree(); 94 95 if (IsInUncomposedDoc()) { 96 if (AttrValueIs(kNameSpaceID_None, nsGkAtoms::rel, nsGkAtoms::localization, 97 eIgnoreCase)) { 98 aContext.OwnerDoc().LocalizationLinkAdded(this); 99 } 100 101 LinkAdded(); 102 } 103 104 return rv; 105 } 106 107 void HTMLLinkElement::LinkAdded() { 108 CreateAndDispatchEvent(u"DOMLinkAdded"_ns); 109 } 110 111 void HTMLLinkElement::UnbindFromTree(UnbindContext& aContext) { 112 CancelDNSPrefetch(*this); 113 CancelPrefetchOrPreload(); 114 115 // If this is reinserted back into the document it will not be 116 // from the parser. 117 Document* oldDoc = GetUncomposedDoc(); 118 ShadowRoot* oldShadowRoot = GetContainingShadow(); 119 120 // We want to update the localization but only if the link is removed from a 121 // DOM change, and not because the document is going away. 122 bool ignore; 123 if (oldDoc) { 124 if (oldDoc->GetScriptHandlingObject(ignore) && 125 AttrValueIs(kNameSpaceID_None, nsGkAtoms::rel, nsGkAtoms::localization, 126 eIgnoreCase)) { 127 oldDoc->LocalizationLinkRemoved(this); 128 } 129 } 130 131 nsGenericHTMLElement::UnbindFromTree(aContext); 132 133 (void)UpdateStyleSheetInternal(oldDoc, oldShadowRoot); 134 } 135 136 bool HTMLLinkElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, 137 const nsAString& aValue, 138 nsIPrincipal* aMaybeScriptedPrincipal, 139 nsAttrValue& aResult) { 140 if (aNamespaceID == kNameSpaceID_None) { 141 if (aAttribute == nsGkAtoms::crossorigin) { 142 ParseCORSValue(aValue, aResult); 143 return true; 144 } 145 146 if (aAttribute == nsGkAtoms::as) { 147 net::ParseAsValue(aValue, aResult); 148 return true; 149 } 150 151 if (aAttribute == nsGkAtoms::sizes) { 152 aResult.ParseAtomArray(aValue); 153 return true; 154 } 155 156 if (aAttribute == nsGkAtoms::integrity) { 157 aResult.ParseStringOrAtom(aValue); 158 return true; 159 } 160 161 if (aAttribute == nsGkAtoms::fetchpriority) { 162 ParseFetchPriority(aValue, aResult); 163 return true; 164 } 165 166 if (aAttribute == nsGkAtoms::blocking && 167 StaticPrefs::dom_element_blocking_enabled()) { 168 aResult.ParseAtomArray(aValue); 169 return true; 170 } 171 } 172 173 return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue, 174 aMaybeScriptedPrincipal, aResult); 175 } 176 177 void HTMLLinkElement::CreateAndDispatchEvent(const nsAString& aEventName) { 178 MOZ_ASSERT(IsInUncomposedDoc()); 179 180 // In the unlikely case that both rev is specified *and* rel=stylesheet, 181 // this code will cause the event to fire, on the principle that maybe the 182 // page really does want to specify that its author is a stylesheet. Since 183 // this should never actually happen and the performance hit is minimal, 184 // doing the "right" thing costs virtually nothing here, even if it doesn't 185 // make much sense. 186 static AttrArray::AttrValuesArray strings[] = { 187 nsGkAtoms::_empty, nsGkAtoms::stylesheet, nullptr}; 188 189 if (!nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None, 190 nsGkAtoms::rev) && 191 FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::rel, strings, 192 eIgnoreCase) != AttrArray::ATTR_VALUE_NO_MATCH) { 193 return; 194 } 195 196 RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher( 197 this, aEventName, CanBubble::eYes, ChromeOnlyDispatch::eYes); 198 // Always run async in order to avoid running script when the content 199 // sink isn't expecting it. 200 asyncDispatcher->PostDOMEvent(); 201 } 202 203 void HTMLLinkElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName, 204 const nsAttrValue* aValue, bool aNotify) { 205 if (aNameSpaceID == kNameSpaceID_None && 206 (aName == nsGkAtoms::href || aName == nsGkAtoms::rel)) { 207 CancelDNSPrefetch(*this); 208 CancelPrefetchOrPreload(); 209 } 210 211 return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName, aValue, 212 aNotify); 213 } 214 215 void HTMLLinkElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, 216 const nsAttrValue* aValue, 217 const nsAttrValue* aOldValue, 218 nsIPrincipal* aSubjectPrincipal, 219 bool aNotify) { 220 if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::href) { 221 mCachedURI = nullptr; 222 if (IsInUncomposedDoc()) { 223 CreateAndDispatchEvent(u"DOMLinkChanged"_ns); 224 } 225 mTriggeringPrincipal = nsContentUtils::GetAttrTriggeringPrincipal( 226 this, nsAttrValueOrString(aValue).String(), aSubjectPrincipal); 227 228 // If the link has `rel=localization` and its `href` attribute is changed, 229 // update the list of localization links. 230 if (AttrValueIs(kNameSpaceID_None, nsGkAtoms::rel, nsGkAtoms::localization, 231 eIgnoreCase)) { 232 if (Document* doc = GetUncomposedDoc()) { 233 if (aOldValue) { 234 doc->LocalizationLinkRemoved(this); 235 } 236 if (aValue) { 237 doc->LocalizationLinkAdded(this); 238 } 239 } 240 } 241 } 242 243 // If a link's `rel` attribute was changed from or to `localization`, 244 // update the list of localization links. 245 if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::rel) { 246 if (Document* doc = GetUncomposedDoc()) { 247 if ((aValue && aValue->Equals(nsGkAtoms::localization, eIgnoreCase)) && 248 (!aOldValue || 249 !aOldValue->Equals(nsGkAtoms::localization, eIgnoreCase))) { 250 doc->LocalizationLinkAdded(this); 251 } else if ((aOldValue && 252 aOldValue->Equals(nsGkAtoms::localization, eIgnoreCase)) && 253 (!aValue || 254 !aValue->Equals(nsGkAtoms::localization, eIgnoreCase))) { 255 doc->LocalizationLinkRemoved(this); 256 } 257 } 258 } 259 260 if (aValue) { 261 if (aNameSpaceID == kNameSpaceID_None && 262 (aName == nsGkAtoms::href || aName == nsGkAtoms::rel || 263 aName == nsGkAtoms::title || aName == nsGkAtoms::media || 264 aName == nsGkAtoms::type || aName == nsGkAtoms::as || 265 aName == nsGkAtoms::crossorigin || aName == nsGkAtoms::disabled)) { 266 bool dropSheet = false; 267 if (aName == nsGkAtoms::rel) { 268 nsAutoString value; 269 aValue->ToString(value); 270 uint32_t linkTypes = ParseLinkTypes(value); 271 if (GetSheet()) { 272 dropSheet = !(linkTypes & eSTYLESHEET); 273 } 274 } 275 276 if ((aName == nsGkAtoms::rel || aName == nsGkAtoms::href) && 277 IsInComposedDoc()) { 278 TryDNSPrefetchOrPreconnectOrPrefetchOrPreloadOrPrerender(); 279 } 280 281 if ((aName == nsGkAtoms::as || aName == nsGkAtoms::type || 282 aName == nsGkAtoms::crossorigin || aName == nsGkAtoms::media) && 283 IsInComposedDoc()) { 284 UpdatePreload(aName, aValue, aOldValue); 285 } 286 287 const bool forceUpdate = 288 dropSheet || aName == nsGkAtoms::title || aName == nsGkAtoms::media || 289 aName == nsGkAtoms::type || aName == nsGkAtoms::disabled; 290 291 (void)UpdateStyleSheetInternal( 292 nullptr, nullptr, forceUpdate ? ForceUpdate::Yes : ForceUpdate::No); 293 } 294 } else { 295 if (aNameSpaceID == kNameSpaceID_None) { 296 if (aName == nsGkAtoms::disabled) { 297 mExplicitlyEnabled = true; 298 } 299 // Since removing href or rel makes us no longer link to a stylesheet, 300 // force updates for those too. 301 if (aName == nsGkAtoms::href || aName == nsGkAtoms::rel || 302 aName == nsGkAtoms::title || aName == nsGkAtoms::media || 303 aName == nsGkAtoms::type || aName == nsGkAtoms::disabled) { 304 (void)UpdateStyleSheetInternal(nullptr, nullptr, ForceUpdate::Yes); 305 } 306 if ((aName == nsGkAtoms::as || aName == nsGkAtoms::type || 307 aName == nsGkAtoms::crossorigin || aName == nsGkAtoms::media) && 308 IsInComposedDoc()) { 309 UpdatePreload(aName, aValue, aOldValue); 310 } 311 } 312 } 313 314 return nsGenericHTMLElement::AfterSetAttr( 315 aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify); 316 } 317 318 // Keep this and the arrays below in sync with ToLinkMask in LinkStyle.cpp. 319 #define SUPPORTED_REL_VALUES_BASE \ 320 "preload", "prefetch", "dns-prefetch", "stylesheet", "next", "alternate", \ 321 "preconnect", "icon", "search", nullptr 322 323 static const DOMTokenListSupportedToken sSupportedRelValueCombinations[][13] = { 324 {SUPPORTED_REL_VALUES_BASE}, 325 {"manifest", SUPPORTED_REL_VALUES_BASE}, 326 {"modulepreload", SUPPORTED_REL_VALUES_BASE}, 327 {"modulepreload", "manifest", SUPPORTED_REL_VALUES_BASE}, 328 {"compression-dictionary", SUPPORTED_REL_VALUES_BASE}, 329 {"compression-dictionary", "manifest", SUPPORTED_REL_VALUES_BASE}, 330 {"compression-dictionary", "modulepreload", SUPPORTED_REL_VALUES_BASE}, 331 {"compression-dictionary", "modulepreload", "manifest", 332 SUPPORTED_REL_VALUES_BASE}}; 333 #undef SUPPORTED_REL_VALUES_BASE 334 335 nsDOMTokenList* HTMLLinkElement::RelList() { 336 if (!mRelList) { 337 int index = (StaticPrefs::dom_manifest_enabled() ? 1 : 0) | 338 (StaticPrefs::network_modulepreload() ? 2 : 0) | 339 (StaticPrefs::network_http_dictionaries_enable() ? 4 : 0); 340 341 mRelList = new nsDOMTokenList(this, nsGkAtoms::rel, 342 sSupportedRelValueCombinations[index]); 343 } 344 return mRelList; 345 } 346 347 Maybe<LinkStyle::SheetInfo> HTMLLinkElement::GetStyleSheetInfo() { 348 nsAutoString rel; 349 GetAttr(nsGkAtoms::rel, rel); 350 uint32_t linkTypes = ParseLinkTypes(rel); 351 if (!(linkTypes & eSTYLESHEET)) { 352 return Nothing(); 353 } 354 355 if (!IsCSSMimeTypeAttributeForLinkElement(*this)) { 356 return Nothing(); 357 } 358 359 if (Disabled()) { 360 return Nothing(); 361 } 362 363 nsAutoString title; 364 nsAutoString media; 365 GetTitleAndMediaForElement(*this, title, media); 366 367 bool alternate = linkTypes & eALTERNATE; 368 if (alternate && title.IsEmpty()) { 369 // alternates must have title. 370 return Nothing(); 371 } 372 373 if (!HasNonEmptyAttr(nsGkAtoms::href)) { 374 return Nothing(); 375 } 376 377 nsAutoString integrity; 378 GetAttr(nsGkAtoms::integrity, integrity); 379 380 nsCOMPtr<nsIURI> uri = GetURI(); 381 nsCOMPtr<nsIPrincipal> prin = mTriggeringPrincipal; 382 383 nsAutoString nonce; 384 nsString* cspNonce = static_cast<nsString*>(GetProperty(nsGkAtoms::nonce)); 385 if (cspNonce) { 386 nonce = *cspNonce; 387 } 388 389 return Some(SheetInfo{ 390 *OwnerDoc(), 391 this, 392 uri.forget(), 393 prin.forget(), 394 MakeAndAddRef<ReferrerInfo>(*this), 395 GetCORSMode(), 396 title, 397 media, 398 integrity, 399 nonce, 400 alternate ? HasAlternateRel::Yes : HasAlternateRel::No, 401 IsInline::No, 402 mExplicitlyEnabled ? IsExplicitlyEnabled::Yes : IsExplicitlyEnabled::No, 403 Element::GetFetchPriority(), 404 }); 405 } 406 407 void HTMLLinkElement::AddSizeOfExcludingThis(nsWindowSizes& aSizes, 408 size_t* aNodeSize) const { 409 nsGenericHTMLElement::AddSizeOfExcludingThis(aSizes, aNodeSize); 410 411 // It is okay to include the size of mCachedURI here even though it might have 412 // strong references from elsewhere because the URI was created for this 413 // object, in nsGenericHTMLElement::GetURIAttr(). Only objects that created 414 // their own URI will call nsIURI::SizeOfIncludingThis(). 415 if (mCachedURI) { 416 *aNodeSize += mCachedURI->SizeOfIncludingThis(aSizes.mState.mMallocSizeOf); 417 } 418 } 419 420 JSObject* HTMLLinkElement::WrapNode(JSContext* aCx, 421 JS::Handle<JSObject*> aGivenProto) { 422 return HTMLLinkElement_Binding::Wrap(aCx, this, aGivenProto); 423 } 424 425 void HTMLLinkElement::GetAs(nsAString& aResult) { 426 GetEnumAttr(nsGkAtoms::as, "", aResult); 427 } 428 429 void HTMLLinkElement::GetContentPolicyMimeTypeMedia( 430 nsAttrValue& aAsAttr, nsContentPolicyType& aPolicyType, nsString& aMimeType, 431 nsAString& aMedia) { 432 nsAutoString as; 433 GetAttr(nsGkAtoms::as, as); 434 net::ParseAsValue(as, aAsAttr); 435 aPolicyType = net::AsValueToContentPolicy(aAsAttr); 436 437 nsAutoString type; 438 GetAttr(nsGkAtoms::type, type); 439 nsAutoString notUsed; 440 nsContentUtils::SplitMimeType(type, aMimeType, notUsed); 441 442 GetAttr(nsGkAtoms::media, aMedia); 443 } 444 445 void HTMLLinkElement:: 446 TryDNSPrefetchOrPreconnectOrPrefetchOrPreloadOrPrerender() { 447 MOZ_ASSERT(IsInComposedDoc()); 448 if (!HasAttr(nsGkAtoms::href)) { 449 return; 450 } 451 452 nsAutoString rel; 453 if (!GetAttr(nsGkAtoms::rel, rel)) { 454 return; 455 } 456 457 if (!nsContentUtils::PrefetchPreloadEnabled(OwnerDoc()->GetDocShell())) { 458 return; 459 } 460 461 uint32_t linkTypes = ParseLinkTypes(rel); 462 463 if ((linkTypes & ePREFETCH) || (linkTypes & eNEXT)) { 464 nsCOMPtr<nsIPrefetchService> prefetchService( 465 components::Prefetch::Service()); 466 if (prefetchService) { 467 if (nsCOMPtr<nsIURI> uri = GetURI()) { 468 auto referrerInfo = MakeRefPtr<ReferrerInfo>(*this); 469 prefetchService->PrefetchURI(uri, referrerInfo, this, 470 linkTypes & ePREFETCH); 471 return; 472 } 473 } 474 } 475 476 if (linkTypes & eCOMPRESSION_DICTIONARY) { 477 if (nsCOMPtr<nsIURI> uri = GetURI()) { 478 StartPreload(nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD); 479 return; 480 } 481 } 482 483 if (linkTypes & ePRELOAD) { 484 if (nsCOMPtr<nsIURI> uri = GetURI()) { 485 nsContentPolicyType policyType; 486 487 nsAttrValue asAttr; 488 nsAutoString mimeType; 489 nsAutoString media; 490 GetContentPolicyMimeTypeMedia(asAttr, policyType, mimeType, media); 491 492 if (policyType == nsIContentPolicy::TYPE_INVALID || 493 !net::CheckPreloadAttrs(asAttr, mimeType, media, OwnerDoc())) { 494 // Ignore preload with a wrong or empty as attribute. 495 net::WarnIgnoredPreload(*OwnerDoc(), *uri); 496 return; 497 } 498 499 // https://html.spec.whatwg.org/#translate-a-preload-destination 500 // If destination is not "fetch", "font", "image", "script", "style", or 501 // "track", then return null. 502 int16_t asValue = asAttr.GetEnumValue(); 503 if (asValue != net::DESTINATION_FETCH && 504 asValue != net::DESTINATION_FONT && 505 asValue != net::DESTINATION_IMAGE && 506 asValue != net::DESTINATION_SCRIPT && 507 asValue != net::DESTINATION_STYLE && 508 asValue != net::DESTINATION_TRACK) { 509 // TODO: Currently the spec doesn't define an event handler to be called 510 // , but this is under discussion. 511 // See: https://github.com/whatwg/html/issues/10940 512 // 513 // Post a "load" event here to match the legacy behavior. 514 RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher( 515 this, u"load"_ns, CanBubble::eNo, ChromeOnlyDispatch::eNo); 516 asyncDispatcher->PostDOMEvent(); 517 return; 518 } 519 520 StartPreload(policyType); 521 return; 522 } 523 } 524 525 if (linkTypes & eMODULE_PRELOAD) { 526 ScriptLoader* scriptLoader = OwnerDoc()->GetScriptLoader(); 527 if (!scriptLoader) { 528 return; 529 } 530 ModuleLoader* moduleLoader = scriptLoader->GetModuleLoader(); 531 532 if (!moduleLoader) { 533 // For the print preview documents, at this moment it doesn't have module 534 // loader yet, as the (print preview) document is not attached to the 535 // nsIDocumentViewer yet, so it doesn't have the GlobalObject. 536 // Also, the script elements won't be processed as they are also cloned 537 // from the original document. 538 // So we simply bail out if the module loader is null. 539 return; 540 } 541 542 if (!StaticPrefs::network_modulepreload()) { 543 // Keep behavior from https://phabricator.services.mozilla.com/D149371, 544 // prior to main implementation of modulepreload 545 moduleLoader->DisallowImportMaps(); 546 return; 547 } 548 549 // https://html.spec.whatwg.org/multipage/semantics.html#processing-the-media-attribute 550 // TODO: apply this check for all linkTypes 551 nsAutoString media; 552 if (GetAttr(nsGkAtoms::media, media)) { 553 RefPtr<mozilla::dom::MediaList> mediaList = 554 mozilla::dom::MediaList::Create(NS_ConvertUTF16toUTF8(media)); 555 if (!mediaList->Matches(*OwnerDoc())) { 556 return; 557 } 558 } 559 560 // TODO: per spec, apply this check for ePREFETCH as well 561 if (!HasNonEmptyAttr(nsGkAtoms::href)) { 562 return; 563 } 564 565 nsAutoString as; 566 GetAttr(nsGkAtoms::as, as); 567 568 if (!net::IsScriptLikeOrInvalid(as)) { 569 RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher( 570 this, u"error"_ns, CanBubble::eNo, ChromeOnlyDispatch::eNo); 571 asyncDispatcher->PostDOMEvent(); 572 return; 573 } 574 575 nsCOMPtr<nsIURI> uri = GetURI(); 576 if (!uri) { 577 return; 578 } 579 580 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-modulepreload-module-script-graph 581 // Step 1. Disallow further import maps given settings object. 582 moduleLoader->DisallowImportMaps(); 583 584 StartPreload(nsIContentPolicy::TYPE_SCRIPT); 585 return; 586 } 587 588 if (linkTypes & ePRECONNECT) { 589 if (nsCOMPtr<nsIURI> uri = GetURI()) { 590 OwnerDoc()->MaybePreconnect( 591 uri, AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin))); 592 return; 593 } 594 } 595 596 if (linkTypes & eDNS_PREFETCH) { 597 TryDNSPrefetch(*this, HTMLDNSPrefetch::PrefetchSource::LinkDnsPrefetch); 598 } 599 } 600 601 void HTMLLinkElement::UpdatePreload(nsAtom* aName, const nsAttrValue* aValue, 602 const nsAttrValue* aOldValue) { 603 MOZ_ASSERT(IsInComposedDoc()); 604 605 if (!HasAttr(nsGkAtoms::href)) { 606 return; 607 } 608 609 nsAutoString rel; 610 if (!GetAttr(nsGkAtoms::rel, rel)) { 611 return; 612 } 613 614 if (!nsContentUtils::PrefetchPreloadEnabled(OwnerDoc()->GetDocShell())) { 615 return; 616 } 617 618 uint32_t linkTypes = ParseLinkTypes(rel); 619 620 if (!(linkTypes & ePRELOAD)) { 621 return; 622 } 623 624 nsCOMPtr<nsIURI> uri = GetURI(); 625 if (!uri) { 626 return; 627 } 628 629 nsAttrValue asAttr; 630 nsContentPolicyType asPolicyType; 631 nsAutoString mimeType; 632 nsAutoString media; 633 GetContentPolicyMimeTypeMedia(asAttr, asPolicyType, mimeType, media); 634 635 if (asPolicyType == nsIContentPolicy::TYPE_INVALID || 636 !net::CheckPreloadAttrs(asAttr, mimeType, media, OwnerDoc())) { 637 // Ignore preload with a wrong or empty as attribute, but be sure to cancel 638 // the old one. 639 CancelPrefetchOrPreload(); 640 net::WarnIgnoredPreload(*OwnerDoc(), *uri); 641 return; 642 } 643 644 if (aName == nsGkAtoms::crossorigin) { 645 CORSMode corsMode = AttrValueToCORSMode(aValue); 646 CORSMode oldCorsMode = AttrValueToCORSMode(aOldValue); 647 if (corsMode != oldCorsMode) { 648 CancelPrefetchOrPreload(); 649 StartPreload(asPolicyType); 650 } 651 return; 652 } 653 654 nsContentPolicyType oldPolicyType; 655 656 if (aName == nsGkAtoms::as) { 657 if (aOldValue) { 658 oldPolicyType = net::AsValueToContentPolicy(*aOldValue); 659 if (!net::CheckPreloadAttrs(*aOldValue, mimeType, media, OwnerDoc())) { 660 oldPolicyType = nsIContentPolicy::TYPE_INVALID; 661 } 662 } else { 663 oldPolicyType = nsIContentPolicy::TYPE_INVALID; 664 } 665 } else if (aName == nsGkAtoms::type) { 666 nsAutoString oldType; 667 nsAutoString notUsed; 668 if (aOldValue) { 669 aOldValue->ToString(oldType); 670 } 671 nsAutoString oldMimeType; 672 nsContentUtils::SplitMimeType(oldType, oldMimeType, notUsed); 673 if (net::CheckPreloadAttrs(asAttr, oldMimeType, media, OwnerDoc())) { 674 oldPolicyType = asPolicyType; 675 } else { 676 oldPolicyType = nsIContentPolicy::TYPE_INVALID; 677 } 678 } else { 679 MOZ_ASSERT(aName == nsGkAtoms::media); 680 nsAutoString oldMedia; 681 if (aOldValue) { 682 aOldValue->ToString(oldMedia); 683 } 684 if (net::CheckPreloadAttrs(asAttr, mimeType, oldMedia, OwnerDoc())) { 685 oldPolicyType = asPolicyType; 686 } else { 687 oldPolicyType = nsIContentPolicy::TYPE_INVALID; 688 } 689 } 690 691 if (asPolicyType != oldPolicyType && 692 oldPolicyType != nsIContentPolicy::TYPE_INVALID) { 693 CancelPrefetchOrPreload(); 694 } 695 696 // Trigger a new preload if the policy type has changed. 697 if (asPolicyType != oldPolicyType) { 698 StartPreload(asPolicyType); 699 } 700 } 701 702 void HTMLLinkElement::CancelPrefetchOrPreload() { 703 CancelPreload(); 704 705 nsCOMPtr<nsIPrefetchService> prefetchService(components::Prefetch::Service()); 706 if (prefetchService) { 707 if (nsCOMPtr<nsIURI> uri = GetURI()) { 708 prefetchService->CancelPrefetchPreloadURI(uri, this); 709 } 710 } 711 } 712 713 void HTMLLinkElement::StartPreload(nsContentPolicyType aPolicyType) { 714 MOZ_ASSERT(!mPreload, "Forgot to cancel the running preload"); 715 RefPtr<PreloaderBase> preload = 716 OwnerDoc()->Preloads().PreloadLinkElement(this, aPolicyType); 717 mPreload = preload.get(); 718 } 719 720 void HTMLLinkElement::CancelPreload() { 721 if (mPreload) { 722 // This will cancel the loading channel if this was the last referred node 723 // and the preload is not used up until now to satisfy a regular tag load 724 // request. 725 mPreload->RemoveLinkPreloadNode(this); 726 mPreload = nullptr; 727 } 728 } 729 730 bool HTMLLinkElement::IsCSSMimeTypeAttributeForLinkElement( 731 const Element& aSelf) { 732 // Processing the type attribute per 733 // https://html.spec.whatwg.org/multipage/semantics.html#processing-the-type-attribute 734 // for HTML link elements. 735 nsAutoString type; 736 nsAutoString mimeType; 737 nsAutoString notUsed; 738 aSelf.GetAttr(nsGkAtoms::type, type); 739 nsContentUtils::SplitMimeType(type, mimeType, notUsed); 740 return mimeType.IsEmpty() || mimeType.LowerCaseEqualsLiteral("text/css"); 741 } 742 743 nsDOMTokenList* HTMLLinkElement::Blocking() { 744 if (!mBlocking) { 745 mBlocking = 746 new nsDOMTokenList(this, nsGkAtoms::blocking, sSupportedBlockingValues); 747 } 748 return mBlocking; 749 } 750 751 bool HTMLLinkElement::IsPotentiallyRenderBlocking() { 752 return BlockingContainsRender(); 753 754 // TODO: handle implicitly potentially render blocking 755 // https://html.spec.whatwg.org/#implicitly-potentially-render-blocking 756 // The default type for resources given by the stylesheet keyword is text/css. 757 // A link element of this type is implicitly potentially render-blocking if 758 // the element was created by its node document's parser. 759 } 760 761 nsresult HTMLLinkElement::CopyInnerTo(HTMLLinkElement* aDest) { 762 nsresult rv = Element::CopyInnerTo(aDest); 763 NS_ENSURE_SUCCESS(rv, rv); 764 MaybeStartCopyStyleSheetTo(aDest, aDest->OwnerDoc()); 765 return NS_OK; 766 } 767 768 } // namespace mozilla::dom