Bug 1265584 [wpt PR 11167] - [Gecko Bug 1265584] Move wptrunner marionette usage...
[gecko.git] / dom / html / HTMLLinkElement.cpp
blobbd0885c4d494a87693d625e972393b7494c9dcb5
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/. */
7 #include "mozilla/dom/HTMLLinkElement.h"
9 #include "mozilla/AsyncEventDispatcher.h"
10 #include "mozilla/Attributes.h"
11 #include "mozilla/EventDispatcher.h"
12 #include "mozilla/EventStates.h"
13 #include "mozilla/MemoryReporting.h"
14 #include "mozilla/Preferences.h"
15 #include "mozilla/dom/HTMLLinkElementBinding.h"
16 #include "nsContentUtils.h"
17 #include "nsGenericHTMLElement.h"
18 #include "nsGkAtoms.h"
19 #include "nsDOMTokenList.h"
20 #include "nsIContentInlines.h"
21 #include "nsIDocument.h"
22 #include "nsINode.h"
23 #include "nsIStyleSheetLinkingElement.h"
24 #include "nsIURL.h"
25 #include "nsPIDOMWindow.h"
26 #include "nsReadableUtils.h"
27 #include "nsStyleConsts.h"
28 #include "nsStyleLinkElement.h"
29 #include "nsUnicharUtils.h"
30 #include "nsWindowSizes.h"
31 #include "nsIContentPolicy.h"
32 #include "nsMimeTypes.h"
33 #include "imgLoader.h"
34 #include "MediaContainerType.h"
35 #include "DecoderDoctorDiagnostics.h"
36 #include "DecoderTraits.h"
37 #include "MediaList.h"
38 #include "nsAttrValueInlines.h"
40 #define LINK_ELEMENT_FLAG_BIT(n_) \
41 NODE_FLAG_BIT(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + (n_))
43 // Link element specific bits
44 enum {
45 // Indicates that a DNS Prefetch has been requested from this Link element.
46 HTML_LINK_DNS_PREFETCH_REQUESTED = LINK_ELEMENT_FLAG_BIT(0),
48 // Indicates that a DNS Prefetch was added to the deferral queue
49 HTML_LINK_DNS_PREFETCH_DEFERRED = LINK_ELEMENT_FLAG_BIT(1)
52 #undef LINK_ELEMENT_FLAG_BIT
54 ASSERT_NODE_FLAGS_SPACE(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + 2);
56 NS_IMPL_NS_NEW_HTML_ELEMENT(Link)
58 namespace mozilla {
59 namespace dom {
61 HTMLLinkElement::HTMLLinkElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
62 : nsGenericHTMLElement(aNodeInfo)
63 , Link(this)
67 HTMLLinkElement::~HTMLLinkElement()
71 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLLinkElement)
73 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLLinkElement,
74 nsGenericHTMLElement)
75 tmp->nsStyleLinkElement::Traverse(cb);
76 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelList)
77 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
79 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLLinkElement,
80 nsGenericHTMLElement)
81 tmp->nsStyleLinkElement::Unlink();
82 NS_IMPL_CYCLE_COLLECTION_UNLINK(mRelList)
83 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
85 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLLinkElement,
86 nsGenericHTMLElement,
87 nsIStyleSheetLinkingElement,
88 Link)
90 NS_IMPL_ELEMENT_CLONE(HTMLLinkElement)
92 bool
93 HTMLLinkElement::Disabled()
95 StyleSheet* ss = GetSheet();
96 return ss && ss->Disabled();
99 void
100 HTMLLinkElement::SetDisabled(bool aDisabled)
102 if (StyleSheet* ss = GetSheet()) {
103 ss->SetDisabled(aDisabled);
107 void
108 HTMLLinkElement::OnDNSPrefetchRequested()
110 UnsetFlags(HTML_LINK_DNS_PREFETCH_DEFERRED);
111 SetFlags(HTML_LINK_DNS_PREFETCH_REQUESTED);
114 void
115 HTMLLinkElement::OnDNSPrefetchDeferred()
117 UnsetFlags(HTML_LINK_DNS_PREFETCH_REQUESTED);
118 SetFlags(HTML_LINK_DNS_PREFETCH_DEFERRED);
121 bool
122 HTMLLinkElement::HasDeferredDNSPrefetchRequest()
124 return HasFlag(HTML_LINK_DNS_PREFETCH_DEFERRED);
127 nsresult
128 HTMLLinkElement::BindToTree(nsIDocument* aDocument,
129 nsIContent* aParent,
130 nsIContent* aBindingParent,
131 bool aCompileEventHandlers)
133 Link::ResetLinkState(false, Link::ElementHasHref());
135 nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
136 aBindingParent,
137 aCompileEventHandlers);
138 NS_ENSURE_SUCCESS(rv, rv);
140 if (nsIDocument* doc = GetComposedDoc()) {
141 doc->RegisterPendingLinkUpdate(this);
142 TryDNSPrefetchOrPreconnectOrPrefetchOrPreloadOrPrerender();
145 void (HTMLLinkElement::*update)() = &HTMLLinkElement::UpdateStyleSheetInternal;
146 nsContentUtils::AddScriptRunner(
147 NewRunnableMethod("dom::HTMLLinkElement::BindToTree", this, update));
149 CreateAndDispatchEvent(aDocument, NS_LITERAL_STRING("DOMLinkAdded"));
151 return rv;
154 void
155 HTMLLinkElement::LinkAdded()
157 CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkAdded"));
160 void
161 HTMLLinkElement::LinkRemoved()
163 CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkRemoved"));
166 void
167 HTMLLinkElement::UnbindFromTree(bool aDeep, bool aNullParent)
169 // Cancel any DNS prefetches
170 // Note: Must come before ResetLinkState. If called after, it will recreate
171 // mCachedURI based on data that is invalid - due to a call to GetHostname.
172 CancelDNSPrefetch(HTML_LINK_DNS_PREFETCH_DEFERRED,
173 HTML_LINK_DNS_PREFETCH_REQUESTED);
174 CancelPrefetchOrPreload();
176 // If this link is ever reinserted into a document, it might
177 // be under a different xml:base, so forget the cached state now.
178 Link::ResetLinkState(false, Link::ElementHasHref());
180 // If this is reinserted back into the document it will not be
181 // from the parser.
182 nsIDocument* oldDoc = GetUncomposedDoc();
183 ShadowRoot* oldShadowRoot = GetContainingShadow();
185 CreateAndDispatchEvent(oldDoc, NS_LITERAL_STRING("DOMLinkRemoved"));
186 nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
188 Unused << UpdateStyleSheetInternal(oldDoc, oldShadowRoot);
191 bool
192 HTMLLinkElement::ParseAttribute(int32_t aNamespaceID,
193 nsAtom* aAttribute,
194 const nsAString& aValue,
195 nsIPrincipal* aMaybeScriptedPrincipal,
196 nsAttrValue& aResult)
198 if (aNamespaceID == kNameSpaceID_None) {
199 if (aAttribute == nsGkAtoms::crossorigin) {
200 ParseCORSValue(aValue, aResult);
201 return true;
204 if (aAttribute == nsGkAtoms::as) {
205 ParseAsValue(aValue, aResult);
206 return true;
209 if (aAttribute == nsGkAtoms::sizes) {
210 aResult.ParseAtomArray(aValue);
211 return true;
214 if (aAttribute == nsGkAtoms::integrity) {
215 aResult.ParseStringOrAtom(aValue);
216 return true;
220 return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
221 aMaybeScriptedPrincipal, aResult);
224 void
225 HTMLLinkElement::CreateAndDispatchEvent(nsIDocument* aDoc,
226 const nsAString& aEventName)
228 if (!aDoc)
229 return;
231 // In the unlikely case that both rev is specified *and* rel=stylesheet,
232 // this code will cause the event to fire, on the principle that maybe the
233 // page really does want to specify that its author is a stylesheet. Since
234 // this should never actually happen and the performance hit is minimal,
235 // doing the "right" thing costs virtually nothing here, even if it doesn't
236 // make much sense.
237 static Element::AttrValuesArray strings[] =
238 {&nsGkAtoms::_empty, &nsGkAtoms::stylesheet, nullptr};
240 if (!nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None,
241 nsGkAtoms::rev) &&
242 FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::rel,
243 strings, eIgnoreCase) != ATTR_VALUE_NO_MATCH)
244 return;
246 RefPtr<AsyncEventDispatcher> asyncDispatcher =
247 new AsyncEventDispatcher(this,
248 aEventName,
249 CanBubble::eYes,
250 ChromeOnlyDispatch::eYes);
251 // Always run async in order to avoid running script when the content
252 // sink isn't expecting it.
253 asyncDispatcher->PostDOMEvent();
256 nsresult
257 HTMLLinkElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
258 const nsAttrValueOrString* aValue, bool aNotify)
260 if (aNameSpaceID == kNameSpaceID_None &&
261 (aName == nsGkAtoms::href || aName == nsGkAtoms::rel)) {
262 CancelDNSPrefetch(HTML_LINK_DNS_PREFETCH_DEFERRED,
263 HTML_LINK_DNS_PREFETCH_REQUESTED);
264 CancelPrefetchOrPreload();
267 return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName,
268 aValue, aNotify);
271 nsresult
272 HTMLLinkElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
273 const nsAttrValue* aValue,
274 const nsAttrValue* aOldValue,
275 nsIPrincipal* aSubjectPrincipal,
276 bool aNotify)
278 // It's safe to call ResetLinkState here because our new attr value has
279 // already been set or unset. ResetLinkState needs the updated attribute
280 // value because notifying the document that content states have changed will
281 // call IntrinsicState, which will try to get updated information about the
282 // visitedness from Link.
283 if (aName == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
284 bool hasHref = aValue;
285 Link::ResetLinkState(!!aNotify, hasHref);
286 if (IsInUncomposedDoc()) {
287 CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkChanged"));
291 if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::href) {
292 mTriggeringPrincipal = nsContentUtils::GetAttrTriggeringPrincipal(
293 this, aValue ? aValue->GetStringValue() : EmptyString(),
294 aSubjectPrincipal);
297 if (aValue) {
298 if (aNameSpaceID == kNameSpaceID_None &&
299 (aName == nsGkAtoms::href ||
300 aName == nsGkAtoms::rel ||
301 aName == nsGkAtoms::title ||
302 aName == nsGkAtoms::media ||
303 aName == nsGkAtoms::type ||
304 aName == nsGkAtoms::as ||
305 aName == nsGkAtoms::crossorigin)) {
306 bool dropSheet = false;
307 if (aName == nsGkAtoms::rel) {
308 nsAutoString value;
309 aValue->ToString(value);
310 uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(value);
311 if (GetSheet()) {
312 dropSheet = !(linkTypes & nsStyleLinkElement::eSTYLESHEET);
316 if ((aName == nsGkAtoms::rel || aName == nsGkAtoms::href) &&
317 IsInComposedDoc()) {
318 TryDNSPrefetchOrPreconnectOrPrefetchOrPreloadOrPrerender();
321 if ((aName == nsGkAtoms::as || aName == nsGkAtoms::type ||
322 aName == nsGkAtoms::crossorigin || aName == nsGkAtoms::media) &&
323 IsInComposedDoc()) {
324 UpdatePreload(aName, aValue, aOldValue);
327 const bool forceUpdate = dropSheet ||
328 aName == nsGkAtoms::title ||
329 aName == nsGkAtoms::media ||
330 aName == nsGkAtoms::type;
332 Unused << UpdateStyleSheetInternal(
333 nullptr, nullptr, forceUpdate ? ForceUpdate::Yes : ForceUpdate::No);
335 } else {
336 // Since removing href or rel makes us no longer link to a
337 // stylesheet, force updates for those too.
338 if (aNameSpaceID == kNameSpaceID_None) {
339 if (aName == nsGkAtoms::href ||
340 aName == nsGkAtoms::rel ||
341 aName == nsGkAtoms::title ||
342 aName == nsGkAtoms::media ||
343 aName == nsGkAtoms::type) {
344 Unused << UpdateStyleSheetInternal(nullptr, nullptr, ForceUpdate::Yes);
346 if ((aName == nsGkAtoms::as || aName == nsGkAtoms::type ||
347 aName == nsGkAtoms::crossorigin || aName == nsGkAtoms::media) &&
348 IsInComposedDoc()) {
349 UpdatePreload(aName, aValue, aOldValue);
354 return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
355 aOldValue, aSubjectPrincipal, aNotify);
358 void
359 HTMLLinkElement::GetEventTargetParent(EventChainPreVisitor& aVisitor)
361 GetEventTargetParentForAnchors(aVisitor);
364 nsresult
365 HTMLLinkElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
367 return PostHandleEventForAnchors(aVisitor);
370 bool
371 HTMLLinkElement::IsLink(nsIURI** aURI) const
373 return IsHTMLLink(aURI);
376 void
377 HTMLLinkElement::GetLinkTarget(nsAString& aTarget)
379 GetAttr(kNameSpaceID_None, nsGkAtoms::target, aTarget);
380 if (aTarget.IsEmpty()) {
381 GetBaseTarget(aTarget);
385 static const DOMTokenListSupportedToken sSupportedRelValues[] = {
386 // Keep this in sync with ToLinkMask in nsStyleLinkElement.cpp.
387 // "preload" must come first because it can be disabled.
388 "preload",
389 "prefetch",
390 "dns-prefetch",
391 "stylesheet",
392 "next",
393 "alternate",
394 "preconnect",
395 "icon",
396 "search",
397 nullptr
400 nsDOMTokenList*
401 HTMLLinkElement::RelList()
403 if (!mRelList) {
404 if (Preferences::GetBool("network.preload")) {
405 mRelList = new nsDOMTokenList(this, nsGkAtoms::rel, sSupportedRelValues);
406 } else {
407 mRelList = new nsDOMTokenList(this, nsGkAtoms::rel, &sSupportedRelValues[1]);
410 return mRelList;
413 already_AddRefed<nsIURI>
414 HTMLLinkElement::GetHrefURI() const
416 return GetHrefURIForAnchors();
419 Maybe<nsStyleLinkElement::SheetInfo>
420 HTMLLinkElement::GetStyleSheetInfo()
422 nsAutoString rel;
423 GetAttr(kNameSpaceID_None, nsGkAtoms::rel, rel);
424 uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(rel);
425 if (!(linkTypes & nsStyleLinkElement::eSTYLESHEET)) {
426 return Nothing();
429 if (!IsCSSMimeTypeAttribute(*this)) {
430 return Nothing();
433 nsAutoString title;
434 nsAutoString media;
435 GetTitleAndMediaForElement(*this, title, media);
437 bool alternate = linkTypes & nsStyleLinkElement::eALTERNATE;
438 if (alternate && title.IsEmpty()) {
439 // alternates must have title.
440 return Nothing();
443 nsAutoString href;
444 GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
445 if (href.IsEmpty()) {
446 return Nothing();
449 nsCOMPtr<nsIURI> uri = Link::GetURI();
450 nsCOMPtr<nsIPrincipal> prin = mTriggeringPrincipal;
451 return Some(SheetInfo {
452 *OwnerDoc(),
453 this,
454 uri.forget(),
455 prin.forget(),
456 GetReferrerPolicyAsEnum(),
457 GetCORSMode(),
458 title,
459 media,
460 alternate ? HasAlternateRel::Yes : HasAlternateRel::No,
461 IsInline::No,
465 EventStates
466 HTMLLinkElement::IntrinsicState() const
468 return Link::LinkState() | nsGenericHTMLElement::IntrinsicState();
471 void
472 HTMLLinkElement::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
473 size_t* aNodeSize) const
475 nsGenericHTMLElement::AddSizeOfExcludingThis(aSizes, aNodeSize);
476 *aNodeSize += Link::SizeOfExcludingThis(aSizes.mState);
479 JSObject*
480 HTMLLinkElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
482 return HTMLLinkElement_Binding::Wrap(aCx, this, aGivenProto);
485 void
486 HTMLLinkElement::GetAs(nsAString& aResult)
488 GetEnumAttr(nsGkAtoms::as, EmptyCString().get(), aResult);
491 // We will use official mime-types from:
492 // https://www.iana.org/assignments/media-types/media-types.xhtml#font
493 // We do not support old deprecated mime-types for preload feature.
494 // (We currectly do not support font/collection)
495 static uint32_t StyleLinkElementFontMimeTypesNum = 5;
496 static const char* StyleLinkElementFontMimeTypes[] = {
497 "font/otf",
498 "font/sfnt",
499 "font/ttf",
500 "font/woff",
501 "font/woff2"
504 bool
505 IsFontMimeType(const nsAString& aType)
507 if (aType.IsEmpty()) {
508 return true;
510 for (uint32_t i = 0; i < StyleLinkElementFontMimeTypesNum; i++) {
511 if (aType.EqualsASCII(StyleLinkElementFontMimeTypes[i])) {
512 return true;
515 return false;
518 bool
519 HTMLLinkElement::CheckPreloadAttrs(const nsAttrValue& aAs,
520 const nsAString& aType,
521 const nsAString& aMedia,
522 nsIDocument* aDocument)
524 nsContentPolicyType policyType = Link::AsValueToContentPolicy(aAs);
525 if (policyType == nsIContentPolicy::TYPE_INVALID) {
526 return false;
529 // Check if media attribute is valid.
530 if (!aMedia.IsEmpty()) {
531 RefPtr<MediaList> mediaList = MediaList::Create(aMedia);
532 nsPresContext* presContext = aDocument->GetPresContext();
533 if (!presContext) {
534 return false;
536 if (!mediaList->Matches(presContext)) {
537 return false;
541 if (aType.IsEmpty()) {
542 return true;
545 nsString type = nsString(aType);
546 ToLowerCase(type);
548 if (policyType == nsIContentPolicy::TYPE_OTHER) {
549 return true;
551 } else if (policyType == nsIContentPolicy::TYPE_MEDIA) {
552 if (aAs.GetEnumValue() == DESTINATION_TRACK) {
553 if (type.EqualsASCII("text/vtt")) {
554 return true;
555 } else {
556 return false;
559 Maybe<MediaContainerType> mimeType = MakeMediaContainerType(aType);
560 if (!mimeType) {
561 return false;
563 DecoderDoctorDiagnostics diagnostics;
564 CanPlayStatus status = DecoderTraits::CanHandleContainerType(*mimeType,
565 &diagnostics);
566 // Preload if this return CANPLAY_YES and CANPLAY_MAYBE.
567 if (status == CANPLAY_NO) {
568 return false;
569 } else {
570 return true;
573 } else if (policyType == nsIContentPolicy::TYPE_FONT) {
574 if (IsFontMimeType(type)) {
575 return true;
576 } else {
577 return false;
580 } else if (policyType == nsIContentPolicy::TYPE_IMAGE) {
581 if (imgLoader::SupportImageWithMimeType(NS_ConvertUTF16toUTF8(type).get(),
582 AcceptedMimeTypes::IMAGES_AND_DOCUMENTS)) {
583 return true;
584 } else {
585 return false;
588 } else if (policyType == nsIContentPolicy::TYPE_SCRIPT) {
589 if (nsContentUtils::IsJavascriptMIMEType(type)) {
590 return true;
591 } else {
592 return false;
595 } else if (policyType == nsIContentPolicy::TYPE_STYLESHEET) {
596 if (type.EqualsASCII("text/css")) {
597 return true;
598 } else {
599 return false;
602 return false;
605 } // namespace dom
606 } // namespace mozilla