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.
142 uri
= net::TryChangeProtocol(uri
, aProtocol
);
146 SetHrefAttribute(uri
);
149 void Link::SetPassword(const nsAString
& aPassword
) {
150 nsCOMPtr
<nsIURI
> uri(GetURI());
152 // Ignore failures to be compatible with NS4.
156 nsresult rv
= NS_MutateURI(uri
)
157 .SetPassword(NS_ConvertUTF16toUTF8(aPassword
))
159 if (NS_SUCCEEDED(rv
)) {
160 SetHrefAttribute(uri
);
164 void Link::SetUsername(const nsAString
& aUsername
) {
165 nsCOMPtr
<nsIURI
> uri(GetURI());
167 // Ignore failures to be compatible with NS4.
171 nsresult rv
= NS_MutateURI(uri
)
172 .SetUsername(NS_ConvertUTF16toUTF8(aUsername
))
174 if (NS_SUCCEEDED(rv
)) {
175 SetHrefAttribute(uri
);
179 void Link::SetHost(const nsAString
& aHost
) {
180 nsCOMPtr
<nsIURI
> uri(GetURI());
182 // Ignore failures to be compatible with NS4.
187 NS_MutateURI(uri
).SetHostPort(NS_ConvertUTF16toUTF8(aHost
)).Finalize(uri
);
191 SetHrefAttribute(uri
);
194 void Link::SetHostname(const nsAString
& aHostname
) {
195 nsCOMPtr
<nsIURI
> uri(GetURI());
197 // Ignore failures to be compatible with NS4.
202 NS_MutateURI(uri
).SetHost(NS_ConvertUTF16toUTF8(aHostname
)).Finalize(uri
);
206 SetHrefAttribute(uri
);
209 void Link::SetPathname(const nsAString
& aPathname
) {
210 nsCOMPtr
<nsIURI
> uri(GetURI());
212 // Ignore failures to be compatible with NS4.
216 nsresult rv
= NS_MutateURI(uri
)
217 .SetFilePath(NS_ConvertUTF16toUTF8(aPathname
))
222 SetHrefAttribute(uri
);
225 void Link::SetSearch(const nsAString
& aSearch
) {
226 nsCOMPtr
<nsIURI
> uri(GetURI());
228 // Ignore failures to be compatible with NS4.
232 auto encoding
= mElement
->OwnerDoc()->GetDocumentCharacterSet();
235 .SetQueryWithEncoding(NS_ConvertUTF16toUTF8(aSearch
), encoding
)
240 SetHrefAttribute(uri
);
243 void Link::SetPort(const nsAString
& aPort
) {
244 nsCOMPtr
<nsIURI
> uri(GetURI());
246 // Ignore failures to be compatible with NS4.
251 nsAutoString
portStr(aPort
);
253 // nsIURI uses -1 as default value.
255 if (!aPort
.IsEmpty()) {
256 port
= portStr
.ToInteger(&rv
);
262 rv
= NS_MutateURI(uri
).SetPort(port
).Finalize(uri
);
266 SetHrefAttribute(uri
);
269 void Link::SetHash(const nsAString
& aHash
) {
270 nsCOMPtr
<nsIURI
> uri(GetURI());
272 // Ignore failures to be compatible with NS4.
277 NS_MutateURI(uri
).SetRef(NS_ConvertUTF16toUTF8(aHash
)).Finalize(uri
);
282 SetHrefAttribute(uri
);
285 void Link::GetOrigin(nsAString
& aOrigin
) {
288 nsCOMPtr
<nsIURI
> uri(GetURI());
294 nsContentUtils::GetWebExposedOriginSerialization(uri
, origin
);
295 aOrigin
.Assign(origin
);
298 void Link::GetProtocol(nsAString
& _protocol
) {
299 nsCOMPtr
<nsIURI
> uri(GetURI());
301 nsAutoCString scheme
;
302 (void)uri
->GetScheme(scheme
);
303 CopyASCIItoUTF16(scheme
, _protocol
);
305 _protocol
.Append(char16_t(':'));
308 void Link::GetUsername(nsAString
& aUsername
) {
309 aUsername
.Truncate();
311 nsCOMPtr
<nsIURI
> uri(GetURI());
316 nsAutoCString username
;
317 uri
->GetUsername(username
);
318 CopyASCIItoUTF16(username
, aUsername
);
321 void Link::GetPassword(nsAString
& aPassword
) {
322 aPassword
.Truncate();
324 nsCOMPtr
<nsIURI
> uri(GetURI());
329 nsAutoCString password
;
330 uri
->GetPassword(password
);
331 CopyASCIItoUTF16(password
, aPassword
);
334 void Link::GetHost(nsAString
& _host
) {
337 nsCOMPtr
<nsIURI
> uri(GetURI());
339 // Do not throw! Not having a valid URI should result in an empty string.
343 nsAutoCString hostport
;
344 nsresult rv
= uri
->GetHostPort(hostport
);
345 if (NS_SUCCEEDED(rv
)) {
346 CopyUTF8toUTF16(hostport
, _host
);
350 void Link::GetHostname(nsAString
& _hostname
) {
351 _hostname
.Truncate();
353 nsCOMPtr
<nsIURI
> uri(GetURI());
355 // Do not throw! Not having a valid URI should result in an empty string.
359 nsContentUtils::GetHostOrIPv6WithBrackets(uri
, _hostname
);
362 void Link::GetPathname(nsAString
& _pathname
) {
363 _pathname
.Truncate();
365 nsCOMPtr
<nsIURI
> uri(GetURI());
367 // Do not throw! Not having a valid URI should result in an empty string.
372 nsresult rv
= uri
->GetFilePath(file
);
373 if (NS_SUCCEEDED(rv
)) {
374 CopyUTF8toUTF16(file
, _pathname
);
378 void Link::GetSearch(nsAString
& _search
) {
381 nsCOMPtr
<nsIURI
> uri(GetURI());
383 // Do not throw! Not having a valid URI or URL should result in an empty
388 nsAutoCString search
;
389 nsresult rv
= uri
->GetQuery(search
);
390 if (NS_SUCCEEDED(rv
) && !search
.IsEmpty()) {
391 _search
.Assign(u
'?');
392 AppendUTF8toUTF16(search
, _search
);
396 void Link::GetPort(nsAString
& _port
) {
399 nsCOMPtr
<nsIURI
> uri(GetURI());
401 // Do not throw! Not having a valid URI should result in an empty string.
406 nsresult rv
= uri
->GetPort(&port
);
407 // Note that failure to get the port from the URI is not necessarily a bad
408 // thing. Some URIs do not have a port.
409 if (NS_SUCCEEDED(rv
) && port
!= -1) {
410 nsAutoString portStr
;
411 portStr
.AppendInt(port
, 10);
412 _port
.Assign(portStr
);
416 void Link::GetHash(nsAString
& _hash
) {
419 nsCOMPtr
<nsIURI
> uri(GetURI());
421 // Do not throw! Not having a valid URI should result in an empty
427 nsresult rv
= uri
->GetRef(ref
);
428 if (NS_SUCCEEDED(rv
) && !ref
.IsEmpty()) {
429 _hash
.Assign(char16_t('#'));
430 AppendUTF8toUTF16(ref
, _hash
);
434 void Link::BindToTree(const BindContext
& aContext
) {
435 if (aContext
.InComposedDoc()) {
436 aContext
.OwnerDoc().RegisterPendingLinkUpdate(this);
438 ResetLinkState(false);
441 void Link::ResetLinkState(bool aNotify
, bool aHasHref
) {
442 // If we have an href, we should register with the history.
444 // FIXME(emilio): Do we really want to allow all MathML elements to be
445 // :visited? That seems not great.
446 mNeedsRegistration
= aHasHref
;
448 // If we've cached the URI, reset always invalidates it.
450 mCachedURI
= nullptr;
452 // Update our state back to the default; the default state for links with an
453 // href is unvisited.
454 SetLinkState(aHasHref
? State::Unvisited
: State::NotLink
, aNotify
);
455 TriggerLinkUpdate(aNotify
);
458 void Link::Unregister() {
459 // If we are not registered, we have nothing to do.
464 MOZ_ASSERT(mHistory
);
465 MOZ_ASSERT(mCachedURI
, "Should unregister before invalidating the URI");
467 // And tell History to stop tracking us.
468 if (nsCOMPtr
<IHistory
> history
= components::History::Service()) {
469 history
->UnregisterVisitedCallback(mCachedURI
, this);
471 mElement
->OwnerDoc()->ForgetLink(this);
475 void Link::SetHrefAttribute(nsIURI
* aURI
) {
476 NS_ASSERTION(aURI
, "Null URI is illegal!");
478 // if we change this code to not reserialize we need to do something smarter
479 // in SetProtocol because changing the protocol of an URI can change the
480 // "nature" of the nsIURL/nsIURI implementation.
482 (void)aURI
->GetSpec(href
);
483 (void)mElement
->SetAttr(kNameSpaceID_None
, nsGkAtoms::href
,
484 NS_ConvertUTF8toUTF16(href
), true);
487 size_t Link::SizeOfExcludingThis(mozilla::SizeOfState
& aState
) const {
490 if (nsCOMPtr
<nsISizeOf
> iface
= do_QueryInterface(mCachedURI
)) {
491 n
+= iface
->SizeOfIncludingThis(aState
.mMallocSizeOf
);
494 // The following members don't need to be measured:
495 // - mElement, because it is a pointer-to-self used to avoid QIs
500 } // namespace mozilla::dom