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"
23 #include "nsIStyleSheetLinkingElement.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
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
)
61 HTMLLinkElement::HTMLLinkElement(already_AddRefed
<mozilla::dom::NodeInfo
>& aNodeInfo
)
62 : nsGenericHTMLElement(aNodeInfo
)
67 HTMLLinkElement::~HTMLLinkElement()
71 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLLinkElement
)
73 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLLinkElement
,
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
,
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
,
87 nsIStyleSheetLinkingElement
,
90 NS_IMPL_ELEMENT_CLONE(HTMLLinkElement
)
93 HTMLLinkElement::Disabled()
95 StyleSheet
* ss
= GetSheet();
96 return ss
&& ss
->Disabled();
100 HTMLLinkElement::SetDisabled(bool aDisabled
)
102 if (StyleSheet
* ss
= GetSheet()) {
103 ss
->SetDisabled(aDisabled
);
108 HTMLLinkElement::OnDNSPrefetchRequested()
110 UnsetFlags(HTML_LINK_DNS_PREFETCH_DEFERRED
);
111 SetFlags(HTML_LINK_DNS_PREFETCH_REQUESTED
);
115 HTMLLinkElement::OnDNSPrefetchDeferred()
117 UnsetFlags(HTML_LINK_DNS_PREFETCH_REQUESTED
);
118 SetFlags(HTML_LINK_DNS_PREFETCH_DEFERRED
);
122 HTMLLinkElement::HasDeferredDNSPrefetchRequest()
124 return HasFlag(HTML_LINK_DNS_PREFETCH_DEFERRED
);
128 HTMLLinkElement::BindToTree(nsIDocument
* aDocument
,
130 nsIContent
* aBindingParent
,
131 bool aCompileEventHandlers
)
133 Link::ResetLinkState(false, Link::ElementHasHref());
135 nsresult rv
= nsGenericHTMLElement::BindToTree(aDocument
, aParent
,
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"));
155 HTMLLinkElement::LinkAdded()
157 CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkAdded"));
161 HTMLLinkElement::LinkRemoved()
163 CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkRemoved"));
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
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
);
192 HTMLLinkElement::ParseAttribute(int32_t aNamespaceID
,
194 const nsAString
& aValue
,
195 nsIPrincipal
* aMaybeScriptedPrincipal
,
196 nsAttrValue
& aResult
)
198 if (aNamespaceID
== kNameSpaceID_None
) {
199 if (aAttribute
== nsGkAtoms::crossorigin
) {
200 ParseCORSValue(aValue
, aResult
);
204 if (aAttribute
== nsGkAtoms::as
) {
205 ParseAsValue(aValue
, aResult
);
209 if (aAttribute
== nsGkAtoms::sizes
) {
210 aResult
.ParseAtomArray(aValue
);
214 if (aAttribute
== nsGkAtoms::integrity
) {
215 aResult
.ParseStringOrAtom(aValue
);
220 return nsGenericHTMLElement::ParseAttribute(aNamespaceID
, aAttribute
, aValue
,
221 aMaybeScriptedPrincipal
, aResult
);
225 HTMLLinkElement::CreateAndDispatchEvent(nsIDocument
* aDoc
,
226 const nsAString
& aEventName
)
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
237 static Element::AttrValuesArray strings
[] =
238 {&nsGkAtoms::_empty
, &nsGkAtoms::stylesheet
, nullptr};
240 if (!nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None
,
242 FindAttrValueIn(kNameSpaceID_None
, nsGkAtoms::rel
,
243 strings
, eIgnoreCase
) != ATTR_VALUE_NO_MATCH
)
246 RefPtr
<AsyncEventDispatcher
> asyncDispatcher
=
247 new AsyncEventDispatcher(this,
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();
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
,
272 HTMLLinkElement::AfterSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
273 const nsAttrValue
* aValue
,
274 const nsAttrValue
* aOldValue
,
275 nsIPrincipal
* aSubjectPrincipal
,
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(),
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
) {
309 aValue
->ToString(value
);
310 uint32_t linkTypes
= nsStyleLinkElement::ParseLinkTypes(value
);
312 dropSheet
= !(linkTypes
& nsStyleLinkElement::eSTYLESHEET
);
316 if ((aName
== nsGkAtoms::rel
|| aName
== nsGkAtoms::href
) &&
318 TryDNSPrefetchOrPreconnectOrPrefetchOrPreloadOrPrerender();
321 if ((aName
== nsGkAtoms::as
|| aName
== nsGkAtoms::type
||
322 aName
== nsGkAtoms::crossorigin
|| aName
== nsGkAtoms::media
) &&
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
);
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
) &&
349 UpdatePreload(aName
, aValue
, aOldValue
);
354 return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID
, aName
, aValue
,
355 aOldValue
, aSubjectPrincipal
, aNotify
);
359 HTMLLinkElement::GetEventTargetParent(EventChainPreVisitor
& aVisitor
)
361 GetEventTargetParentForAnchors(aVisitor
);
365 HTMLLinkElement::PostHandleEvent(EventChainPostVisitor
& aVisitor
)
367 return PostHandleEventForAnchors(aVisitor
);
371 HTMLLinkElement::IsLink(nsIURI
** aURI
) const
373 return IsHTMLLink(aURI
);
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.
401 HTMLLinkElement::RelList()
404 if (Preferences::GetBool("network.preload")) {
405 mRelList
= new nsDOMTokenList(this, nsGkAtoms::rel
, sSupportedRelValues
);
407 mRelList
= new nsDOMTokenList(this, nsGkAtoms::rel
, &sSupportedRelValues
[1]);
413 already_AddRefed
<nsIURI
>
414 HTMLLinkElement::GetHrefURI() const
416 return GetHrefURIForAnchors();
419 Maybe
<nsStyleLinkElement::SheetInfo
>
420 HTMLLinkElement::GetStyleSheetInfo()
423 GetAttr(kNameSpaceID_None
, nsGkAtoms::rel
, rel
);
424 uint32_t linkTypes
= nsStyleLinkElement::ParseLinkTypes(rel
);
425 if (!(linkTypes
& nsStyleLinkElement::eSTYLESHEET
)) {
429 if (!IsCSSMimeTypeAttribute(*this)) {
435 GetTitleAndMediaForElement(*this, title
, media
);
437 bool alternate
= linkTypes
& nsStyleLinkElement::eALTERNATE
;
438 if (alternate
&& title
.IsEmpty()) {
439 // alternates must have title.
444 GetAttr(kNameSpaceID_None
, nsGkAtoms::href
, href
);
445 if (href
.IsEmpty()) {
449 nsCOMPtr
<nsIURI
> uri
= Link::GetURI();
450 nsCOMPtr
<nsIPrincipal
> prin
= mTriggeringPrincipal
;
451 return Some(SheetInfo
{
456 GetReferrerPolicyAsEnum(),
460 alternate
? HasAlternateRel::Yes
: HasAlternateRel::No
,
466 HTMLLinkElement::IntrinsicState() const
468 return Link::LinkState() | nsGenericHTMLElement::IntrinsicState();
472 HTMLLinkElement::AddSizeOfExcludingThis(nsWindowSizes
& aSizes
,
473 size_t* aNodeSize
) const
475 nsGenericHTMLElement::AddSizeOfExcludingThis(aSizes
, aNodeSize
);
476 *aNodeSize
+= Link::SizeOfExcludingThis(aSizes
.mState
);
480 HTMLLinkElement::WrapNode(JSContext
* aCx
, JS::Handle
<JSObject
*> aGivenProto
)
482 return HTMLLinkElement_Binding::Wrap(aCx
, this, aGivenProto
);
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
[] = {
505 IsFontMimeType(const nsAString
& aType
)
507 if (aType
.IsEmpty()) {
510 for (uint32_t i
= 0; i
< StyleLinkElementFontMimeTypesNum
; i
++) {
511 if (aType
.EqualsASCII(StyleLinkElementFontMimeTypes
[i
])) {
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
) {
529 // Check if media attribute is valid.
530 if (!aMedia
.IsEmpty()) {
531 RefPtr
<MediaList
> mediaList
= MediaList::Create(aMedia
);
532 nsPresContext
* presContext
= aDocument
->GetPresContext();
536 if (!mediaList
->Matches(presContext
)) {
541 if (aType
.IsEmpty()) {
545 nsString type
= nsString(aType
);
548 if (policyType
== nsIContentPolicy::TYPE_OTHER
) {
551 } else if (policyType
== nsIContentPolicy::TYPE_MEDIA
) {
552 if (aAs
.GetEnumValue() == DESTINATION_TRACK
) {
553 if (type
.EqualsASCII("text/vtt")) {
559 Maybe
<MediaContainerType
> mimeType
= MakeMediaContainerType(aType
);
563 DecoderDoctorDiagnostics diagnostics
;
564 CanPlayStatus status
= DecoderTraits::CanHandleContainerType(*mimeType
,
566 // Preload if this return CANPLAY_YES and CANPLAY_MAYBE.
567 if (status
== CANPLAY_NO
) {
573 } else if (policyType
== nsIContentPolicy::TYPE_FONT
) {
574 if (IsFontMimeType(type
)) {
580 } else if (policyType
== nsIContentPolicy::TYPE_IMAGE
) {
581 if (imgLoader::SupportImageWithMimeType(NS_ConvertUTF16toUTF8(type
).get(),
582 AcceptedMimeTypes::IMAGES_AND_DOCUMENTS
)) {
588 } else if (policyType
== nsIContentPolicy::TYPE_SCRIPT
) {
589 if (nsContentUtils::IsJavascriptMIMEType(type
)) {
595 } else if (policyType
== nsIContentPolicy::TYPE_STYLESHEET
) {
596 if (type
.EqualsASCII("text/css")) {
606 } // namespace mozilla