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/. */
9 #include "mozilla/MemoryReporting.h"
10 #include "mozilla/dom/Element.h"
11 #include "mozilla/dom/BindContext.h"
12 #include "mozilla/dom/SVGAElement.h"
13 #include "mozilla/dom/HTMLDNSPrefetch.h"
14 #include "mozilla/IHistory.h"
15 #include "mozilla/StaticPrefs_layout.h"
16 #include "nsLayoutUtils.h"
18 #include "nsIURIMutator.h"
19 #include "nsISizeOf.h"
22 #include "nsGkAtoms.h"
24 #include "mozAutoDocUpdate.h"
26 #include "mozilla/Components.h"
27 #include "nsAttrValueInlines.h"
28 #include "HTMLLinkElement.h"
30 namespace mozilla::dom
{
32 Link::Link(Element
* aElement
)
34 mNeedsRegistration(false),
36 mHasPendingLinkUpdate(false),
38 MOZ_ASSERT(mElement
, "Must have an element");
43 mNeedsRegistration(false),
45 mHasPendingLinkUpdate(false),
49 // !mElement is for mock_Link.
50 MOZ_ASSERT(!mElement
|| !mElement
->IsInComposedDoc());
54 bool Link::ElementHasHref() const {
55 if (mElement
->HasAttr(nsGkAtoms::href
)) {
58 if (const auto* svg
= SVGAElement::FromNode(*mElement
)) {
59 // This can be a HasAttr(kNameSpaceID_XLink, nsGkAtoms::href) check once
60 // SMIL is fixed to actually mutate DOM attributes rather than faking it.
61 return svg
->HasHref();
63 MOZ_ASSERT(!mElement
->IsSVGElement(),
64 "What other SVG element inherits from Link?");
68 void Link::SetLinkState(State aState
, bool aNotify
) {
69 Element::AutoStateChangeNotifier
notifier(*mElement
, aNotify
);
72 mElement
->AddStatesSilently(ElementState::VISITED
);
73 mElement
->RemoveStatesSilently(ElementState::UNVISITED
);
75 case State::Unvisited
:
76 mElement
->AddStatesSilently(ElementState::UNVISITED
);
77 mElement
->RemoveStatesSilently(ElementState::VISITED
);
80 mElement
->RemoveStatesSilently(ElementState::VISITED_OR_UNVISITED
);
85 void Link::TriggerLinkUpdate(bool aNotify
) {
86 if (mRegistered
|| !mNeedsRegistration
|| mHasPendingLinkUpdate
||
87 !mElement
->IsInComposedDoc()) {
91 // Only try and register once.
92 mNeedsRegistration
= false;
94 nsCOMPtr
<nsIURI
> hrefURI
= GetURI();
96 // Assume that we are not visited until we are told otherwise.
97 SetLinkState(State::Unvisited
, aNotify
);
99 // Make sure the href attribute has a valid link (bug 23209).
100 // If we have a good href, register with History if available.
101 if (mHistory
&& hrefURI
) {
102 if (nsCOMPtr
<IHistory
> history
= components::History::Service()) {
104 history
->RegisterVisitedCallback(hrefURI
, this);
105 // And make sure we are in the document's link map.
106 mElement
->GetComposedDoc()->AddStyleRelevantLink(this);
111 void Link::VisitedQueryFinished(bool aVisited
) {
112 MOZ_ASSERT(mRegistered
, "Setting the link state of an unregistered Link!");
114 SetLinkState(aVisited
? State::Visited
: State::Unvisited
,
115 /* aNotify = */ true);
116 // Even if the state didn't actually change, we need to repaint in order for
117 // the visited state not to be observable.
118 nsLayoutUtils::PostRestyleEvent(GetElement(), RestyleHint::RestyleSubtree(),
119 nsChangeHint_RepaintFrame
);
122 nsIURI
* Link::GetURI() const {
123 // If we have this URI cached, use it.
128 // Otherwise obtain it.
129 Link
* self
= const_cast<Link
*>(this);
130 Element
* element
= self
->mElement
;
131 mCachedURI
= element
->GetHrefURI();
136 void Link::SetProtocol(const nsAString
& aProtocol
) {
137 nsCOMPtr
<nsIURI
> uri(GetURI());
139 // Ignore failures to be compatible with NS4.
143 nsAString::const_iterator start
, end
;
144 aProtocol
.BeginReading(start
);
145 aProtocol
.EndReading(end
);
146 nsAString::const_iterator
iter(start
);
147 (void)FindCharInReadable(':', iter
, end
);
148 nsresult rv
= NS_MutateURI(uri
)
149 .SetScheme(NS_ConvertUTF16toUTF8(Substring(start
, iter
)))
155 SetHrefAttribute(uri
);
158 void Link::SetPassword(const nsAString
& aPassword
) {
159 nsCOMPtr
<nsIURI
> uri(GetURI());
161 // Ignore failures to be compatible with NS4.
165 nsresult rv
= NS_MutateURI(uri
)
166 .SetPassword(NS_ConvertUTF16toUTF8(aPassword
))
168 if (NS_SUCCEEDED(rv
)) {
169 SetHrefAttribute(uri
);
173 void Link::SetUsername(const nsAString
& aUsername
) {
174 nsCOMPtr
<nsIURI
> uri(GetURI());
176 // Ignore failures to be compatible with NS4.
180 nsresult rv
= NS_MutateURI(uri
)
181 .SetUsername(NS_ConvertUTF16toUTF8(aUsername
))
183 if (NS_SUCCEEDED(rv
)) {
184 SetHrefAttribute(uri
);
188 void Link::SetHost(const nsAString
& aHost
) {
189 nsCOMPtr
<nsIURI
> uri(GetURI());
191 // Ignore failures to be compatible with NS4.
196 NS_MutateURI(uri
).SetHostPort(NS_ConvertUTF16toUTF8(aHost
)).Finalize(uri
);
200 SetHrefAttribute(uri
);
203 void Link::SetHostname(const nsAString
& aHostname
) {
204 nsCOMPtr
<nsIURI
> uri(GetURI());
206 // Ignore failures to be compatible with NS4.
211 NS_MutateURI(uri
).SetHost(NS_ConvertUTF16toUTF8(aHostname
)).Finalize(uri
);
215 SetHrefAttribute(uri
);
218 void Link::SetPathname(const nsAString
& aPathname
) {
219 nsCOMPtr
<nsIURI
> uri(GetURI());
221 // Ignore failures to be compatible with NS4.
225 nsresult rv
= NS_MutateURI(uri
)
226 .SetFilePath(NS_ConvertUTF16toUTF8(aPathname
))
231 SetHrefAttribute(uri
);
234 void Link::SetSearch(const nsAString
& aSearch
) {
235 nsCOMPtr
<nsIURI
> uri(GetURI());
237 // Ignore failures to be compatible with NS4.
241 auto encoding
= mElement
->OwnerDoc()->GetDocumentCharacterSet();
244 .SetQueryWithEncoding(NS_ConvertUTF16toUTF8(aSearch
), encoding
)
249 SetHrefAttribute(uri
);
252 void Link::SetPort(const nsAString
& aPort
) {
253 nsCOMPtr
<nsIURI
> uri(GetURI());
255 // Ignore failures to be compatible with NS4.
260 nsAutoString
portStr(aPort
);
262 // nsIURI uses -1 as default value.
264 if (!aPort
.IsEmpty()) {
265 port
= portStr
.ToInteger(&rv
);
271 rv
= NS_MutateURI(uri
).SetPort(port
).Finalize(uri
);
275 SetHrefAttribute(uri
);
278 void Link::SetHash(const nsAString
& aHash
) {
279 nsCOMPtr
<nsIURI
> uri(GetURI());
281 // Ignore failures to be compatible with NS4.
286 NS_MutateURI(uri
).SetRef(NS_ConvertUTF16toUTF8(aHash
)).Finalize(uri
);
291 SetHrefAttribute(uri
);
294 void Link::GetOrigin(nsAString
& aOrigin
) {
297 nsCOMPtr
<nsIURI
> uri(GetURI());
303 nsContentUtils::GetWebExposedOriginSerialization(uri
, origin
);
304 aOrigin
.Assign(origin
);
307 void Link::GetProtocol(nsAString
& _protocol
) {
308 nsCOMPtr
<nsIURI
> uri(GetURI());
310 nsAutoCString scheme
;
311 (void)uri
->GetScheme(scheme
);
312 CopyASCIItoUTF16(scheme
, _protocol
);
314 _protocol
.Append(char16_t(':'));
317 void Link::GetUsername(nsAString
& aUsername
) {
318 aUsername
.Truncate();
320 nsCOMPtr
<nsIURI
> uri(GetURI());
325 nsAutoCString username
;
326 uri
->GetUsername(username
);
327 CopyASCIItoUTF16(username
, aUsername
);
330 void Link::GetPassword(nsAString
& aPassword
) {
331 aPassword
.Truncate();
333 nsCOMPtr
<nsIURI
> uri(GetURI());
338 nsAutoCString password
;
339 uri
->GetPassword(password
);
340 CopyASCIItoUTF16(password
, aPassword
);
343 void Link::GetHost(nsAString
& _host
) {
346 nsCOMPtr
<nsIURI
> uri(GetURI());
348 // Do not throw! Not having a valid URI should result in an empty string.
352 nsAutoCString hostport
;
353 nsresult rv
= uri
->GetHostPort(hostport
);
354 if (NS_SUCCEEDED(rv
)) {
355 CopyUTF8toUTF16(hostport
, _host
);
359 void Link::GetHostname(nsAString
& _hostname
) {
360 _hostname
.Truncate();
362 nsCOMPtr
<nsIURI
> uri(GetURI());
364 // Do not throw! Not having a valid URI should result in an empty string.
368 nsContentUtils::GetHostOrIPv6WithBrackets(uri
, _hostname
);
371 void Link::GetPathname(nsAString
& _pathname
) {
372 _pathname
.Truncate();
374 nsCOMPtr
<nsIURI
> uri(GetURI());
376 // Do not throw! Not having a valid URI should result in an empty string.
381 nsresult rv
= uri
->GetFilePath(file
);
382 if (NS_SUCCEEDED(rv
)) {
383 CopyUTF8toUTF16(file
, _pathname
);
387 void Link::GetSearch(nsAString
& _search
) {
390 nsCOMPtr
<nsIURI
> uri(GetURI());
392 // Do not throw! Not having a valid URI or URL should result in an empty
397 nsAutoCString search
;
398 nsresult rv
= uri
->GetQuery(search
);
399 if (NS_SUCCEEDED(rv
) && !search
.IsEmpty()) {
400 _search
.Assign(u
'?');
401 AppendUTF8toUTF16(search
, _search
);
405 void Link::GetPort(nsAString
& _port
) {
408 nsCOMPtr
<nsIURI
> uri(GetURI());
410 // Do not throw! Not having a valid URI should result in an empty string.
415 nsresult rv
= uri
->GetPort(&port
);
416 // Note that failure to get the port from the URI is not necessarily a bad
417 // thing. Some URIs do not have a port.
418 if (NS_SUCCEEDED(rv
) && port
!= -1) {
419 nsAutoString portStr
;
420 portStr
.AppendInt(port
, 10);
421 _port
.Assign(portStr
);
425 void Link::GetHash(nsAString
& _hash
) {
428 nsCOMPtr
<nsIURI
> uri(GetURI());
430 // Do not throw! Not having a valid URI should result in an empty
436 nsresult rv
= uri
->GetRef(ref
);
437 if (NS_SUCCEEDED(rv
) && !ref
.IsEmpty()) {
438 _hash
.Assign(char16_t('#'));
439 AppendUTF8toUTF16(ref
, _hash
);
443 void Link::BindToTree(const BindContext
& aContext
) {
444 if (aContext
.InComposedDoc()) {
445 aContext
.OwnerDoc().RegisterPendingLinkUpdate(this);
447 ResetLinkState(false);
450 void Link::ResetLinkState(bool aNotify
, bool aHasHref
) {
451 // If we have an href, we should register with the history.
453 // FIXME(emilio): Do we really want to allow all MathML elements to be
454 // :visited? That seems not great.
455 mNeedsRegistration
= aHasHref
;
457 // If we've cached the URI, reset always invalidates it.
459 mCachedURI
= nullptr;
461 // Update our state back to the default; the default state for links with an
462 // href is unvisited.
463 SetLinkState(aHasHref
? State::Unvisited
: State::NotLink
, aNotify
);
464 TriggerLinkUpdate(aNotify
);
467 void Link::Unregister() {
468 // If we are not registered, we have nothing to do.
473 MOZ_ASSERT(mHistory
);
474 MOZ_ASSERT(mCachedURI
, "Should unregister before invalidating the URI");
476 // And tell History to stop tracking us.
477 if (nsCOMPtr
<IHistory
> history
= components::History::Service()) {
478 history
->UnregisterVisitedCallback(mCachedURI
, this);
480 mElement
->OwnerDoc()->ForgetLink(this);
484 void Link::SetHrefAttribute(nsIURI
* aURI
) {
485 NS_ASSERTION(aURI
, "Null URI is illegal!");
487 // if we change this code to not reserialize we need to do something smarter
488 // in SetProtocol because changing the protocol of an URI can change the
489 // "nature" of the nsIURL/nsIURI implementation.
491 (void)aURI
->GetSpec(href
);
492 (void)mElement
->SetAttr(kNameSpaceID_None
, nsGkAtoms::href
,
493 NS_ConvertUTF8toUTF16(href
), true);
496 size_t Link::SizeOfExcludingThis(mozilla::SizeOfState
& aState
) const {
499 if (nsCOMPtr
<nsISizeOf
> iface
= do_QueryInterface(mCachedURI
)) {
500 n
+= iface
->SizeOfIncludingThis(aState
.mMallocSizeOf
);
503 // The following members don't need to be measured:
504 // - mElement, because it is a pointer-to-self used to avoid QIs
509 } // namespace mozilla::dom