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/dom/Element.h"
10 #include "mozilla/dom/BindContext.h"
11 #include "mozilla/dom/Document.h"
12 #include "mozilla/dom/SVGAElement.h"
13 #include "mozilla/dom/HTMLDNSPrefetch.h"
14 #include "mozilla/IHistory.h"
15 #include "nsLayoutUtils.h"
16 #include "nsIURIMutator.h"
17 #include "nsISizeOf.h"
19 #include "nsGkAtoms.h"
22 #include "mozilla/Components.h"
23 #include "nsAttrValueInlines.h"
25 namespace mozilla::dom
{
27 Link::Link(Element
* aElement
)
29 mNeedsRegistration(false),
31 mHasPendingLinkUpdate(false),
33 MOZ_ASSERT(mElement
, "Must have an element");
38 mNeedsRegistration(false),
40 mHasPendingLinkUpdate(false),
44 // !mElement is for mock_Link.
45 MOZ_ASSERT(!mElement
|| !mElement
->IsInComposedDoc());
49 bool Link::ElementHasHref() const {
50 if (mElement
->HasAttr(nsGkAtoms::href
)) {
53 if (const auto* svg
= SVGAElement::FromNode(*mElement
)) {
54 // This can be a HasAttr(kNameSpaceID_XLink, nsGkAtoms::href) check once
55 // SMIL is fixed to actually mutate DOM attributes rather than faking it.
56 return svg
->HasHref();
58 MOZ_ASSERT(!mElement
->IsSVGElement(),
59 "What other SVG element inherits from Link?");
63 void Link::SetLinkState(State aState
, bool aNotify
) {
64 Element::AutoStateChangeNotifier
notifier(*mElement
, aNotify
);
67 mElement
->AddStatesSilently(ElementState::VISITED
);
68 mElement
->RemoveStatesSilently(ElementState::UNVISITED
);
70 case State::Unvisited
:
71 mElement
->AddStatesSilently(ElementState::UNVISITED
);
72 mElement
->RemoveStatesSilently(ElementState::VISITED
);
75 mElement
->RemoveStatesSilently(ElementState::VISITED_OR_UNVISITED
);
80 void Link::TriggerLinkUpdate(bool aNotify
) {
81 if (mRegistered
|| !mNeedsRegistration
|| mHasPendingLinkUpdate
||
82 !mElement
->IsInComposedDoc()) {
86 // Only try and register once.
87 mNeedsRegistration
= false;
89 nsCOMPtr
<nsIURI
> hrefURI
= GetURI();
91 // Assume that we are not visited until we are told otherwise.
92 SetLinkState(State::Unvisited
, aNotify
);
94 // Make sure the href attribute has a valid link (bug 23209).
95 // If we have a good href, register with History if available.
96 if (mHistory
&& hrefURI
) {
97 if (nsCOMPtr
<IHistory
> history
= components::History::Service()) {
99 history
->RegisterVisitedCallback(hrefURI
, this);
100 // And make sure we are in the document's link map.
101 mElement
->GetComposedDoc()->AddStyleRelevantLink(this);
106 void Link::VisitedQueryFinished(bool aVisited
) {
107 MOZ_ASSERT(mRegistered
, "Setting the link state of an unregistered Link!");
109 SetLinkState(aVisited
? State::Visited
: State::Unvisited
,
110 /* aNotify = */ true);
111 // Even if the state didn't actually change, we need to repaint in order for
112 // the visited state not to be observable.
113 nsLayoutUtils::PostRestyleEvent(GetElement(), RestyleHint::RestyleSubtree(),
114 nsChangeHint_RepaintFrame
);
117 nsIURI
* Link::GetURI() const {
118 // If we have this URI cached, use it.
123 // Otherwise obtain it.
124 Link
* self
= const_cast<Link
*>(this);
125 Element
* element
= self
->mElement
;
126 mCachedURI
= element
->GetHrefURI();
131 void Link::SetProtocol(const nsACString
& aProtocol
) {
132 nsCOMPtr
<nsIURI
> uri(GetURI());
134 // Ignore failures to be compatible with NS4.
137 uri
= net::TryChangeProtocol(uri
, aProtocol
);
141 SetHrefAttribute(uri
);
144 void Link::SetPassword(const nsACString
& aPassword
) {
145 nsCOMPtr
<nsIURI
> uri(GetURI());
147 // Ignore failures to be compatible with NS4.
151 nsresult rv
= NS_MutateURI(uri
).SetPassword(aPassword
).Finalize(uri
);
152 if (NS_SUCCEEDED(rv
)) {
153 SetHrefAttribute(uri
);
157 void Link::SetUsername(const nsACString
& aUsername
) {
158 nsCOMPtr
<nsIURI
> uri(GetURI());
160 // Ignore failures to be compatible with NS4.
164 nsresult rv
= NS_MutateURI(uri
).SetUsername(aUsername
).Finalize(uri
);
165 if (NS_SUCCEEDED(rv
)) {
166 SetHrefAttribute(uri
);
170 void Link::SetHost(const nsACString
& aHost
) {
171 nsCOMPtr
<nsIURI
> uri(GetURI());
173 // Ignore failures to be compatible with NS4.
177 nsresult rv
= NS_MutateURI(uri
).SetHostPort(aHost
).Finalize(uri
);
181 SetHrefAttribute(uri
);
184 void Link::SetHostname(const nsACString
& aHostname
) {
185 nsCOMPtr
<nsIURI
> uri(GetURI());
187 // Ignore failures to be compatible with NS4.
191 nsresult rv
= NS_MutateURI(uri
).SetHost(aHostname
).Finalize(uri
);
195 SetHrefAttribute(uri
);
198 void Link::SetPathname(const nsACString
& aPathname
) {
199 nsCOMPtr
<nsIURI
> uri(GetURI());
201 // Ignore failures to be compatible with NS4.
205 nsresult rv
= NS_MutateURI(uri
).SetFilePath(aPathname
).Finalize(uri
);
209 SetHrefAttribute(uri
);
212 void Link::SetSearch(const nsACString
& aSearch
) {
213 nsCOMPtr
<nsIURI
> uri(GetURI());
215 // Ignore failures to be compatible with NS4.
219 nsresult rv
= NS_MutateURI(uri
).SetQuery(aSearch
).Finalize(uri
);
223 SetHrefAttribute(uri
);
226 void Link::SetPort(const nsACString
& aPort
) {
227 nsCOMPtr
<nsIURI
> uri(GetURI());
229 // Ignore failures to be compatible with NS4.
233 // nsIURI uses -1 as default value.
236 if (!aPort
.IsEmpty()) {
237 port
= aPort
.ToInteger(&rv
);
243 rv
= NS_MutateURI(uri
).SetPort(port
).Finalize(uri
);
247 SetHrefAttribute(uri
);
250 void Link::SetHash(const nsACString
& aHash
) {
251 nsCOMPtr
<nsIURI
> uri(GetURI());
253 // Ignore failures to be compatible with NS4.
257 nsresult rv
= NS_MutateURI(uri
).SetRef(aHash
).Finalize(uri
);
262 SetHrefAttribute(uri
);
265 void Link::GetOrigin(nsACString
& aOrigin
) {
268 nsIURI
* uri
= GetURI();
273 nsContentUtils::GetWebExposedOriginSerialization(uri
, aOrigin
);
276 void Link::GetProtocol(nsACString
& aProtocol
) {
277 if (nsIURI
* uri
= GetURI()) {
278 (void)uri
->GetScheme(aProtocol
);
280 aProtocol
.Append(':');
283 void Link::GetUsername(nsACString
& aUsername
) {
284 aUsername
.Truncate();
286 nsIURI
* uri
= GetURI();
291 uri
->GetUsername(aUsername
);
294 void Link::GetPassword(nsACString
& aPassword
) {
295 aPassword
.Truncate();
297 nsIURI
* uri
= GetURI();
302 uri
->GetPassword(aPassword
);
305 void Link::GetHost(nsACString
& aHost
) {
308 nsIURI
* uri
= GetURI();
310 // Do not throw! Not having a valid URI should result in an empty string.
314 uri
->GetHostPort(aHost
);
317 void Link::GetHostname(nsACString
& aHostname
) {
318 aHostname
.Truncate();
320 nsIURI
* uri
= GetURI();
322 // Do not throw! Not having a valid URI should result in an empty string.
326 nsContentUtils::GetHostOrIPv6WithBrackets(uri
, aHostname
);
329 void Link::GetPathname(nsACString
& aPathname
) {
330 aPathname
.Truncate();
332 nsIURI
* uri
= GetURI();
334 // Do not throw! Not having a valid URI should result in an empty string.
338 uri
->GetFilePath(aPathname
);
341 void Link::GetSearch(nsACString
& aSearch
) {
344 nsIURI
* uri
= GetURI();
346 // Do not throw! Not having a valid URI or URL should result in an empty
351 nsresult rv
= uri
->GetQuery(aSearch
);
352 if (NS_SUCCEEDED(rv
) && !aSearch
.IsEmpty()) {
353 aSearch
.Insert('?', 0);
357 void Link::GetPort(nsACString
& aPort
) {
360 nsIURI
* uri
= GetURI();
362 // Do not throw! Not having a valid URI should result in an empty string.
367 nsresult rv
= uri
->GetPort(&port
);
368 // Note that failure to get the port from the URI is not necessarily a bad
369 // thing. Some URIs do not have a port.
370 if (NS_SUCCEEDED(rv
) && port
!= -1) {
371 aPort
.AppendInt(port
, 10);
375 void Link::GetHash(nsACString
& aHash
) {
378 nsIURI
* uri
= GetURI();
380 // Do not throw! Not having a valid URI should result in an empty
385 nsresult rv
= uri
->GetRef(aHash
);
386 if (NS_SUCCEEDED(rv
) && !aHash
.IsEmpty()) {
387 aHash
.Insert('#', 0);
391 void Link::BindToTree(const BindContext
& aContext
) {
392 if (aContext
.InComposedDoc()) {
393 aContext
.OwnerDoc().RegisterPendingLinkUpdate(this);
395 ResetLinkState(false);
398 void Link::ResetLinkState(bool aNotify
, bool aHasHref
) {
399 // If we have an href, we should register with the history.
401 // FIXME(emilio): Do we really want to allow all MathML elements to be
402 // :visited? That seems not great.
403 mNeedsRegistration
= aHasHref
;
405 // If we've cached the URI, reset always invalidates it.
407 mCachedURI
= nullptr;
409 // Update our state back to the default; the default state for links with an
410 // href is unvisited.
411 SetLinkState(aHasHref
? State::Unvisited
: State::NotLink
, aNotify
);
412 TriggerLinkUpdate(aNotify
);
415 void Link::Unregister() {
416 // If we are not registered, we have nothing to do.
421 MOZ_ASSERT(mHistory
);
422 MOZ_ASSERT(mCachedURI
, "Should unregister before invalidating the URI");
424 // And tell History to stop tracking us.
425 if (nsCOMPtr
<IHistory
> history
= components::History::Service()) {
426 history
->UnregisterVisitedCallback(mCachedURI
, this);
428 mElement
->OwnerDoc()->ForgetLink(this);
432 void Link::SetHrefAttribute(nsIURI
* aURI
) {
433 NS_ASSERTION(aURI
, "Null URI is illegal!");
435 // if we change this code to not reserialize we need to do something smarter
436 // in SetProtocol because changing the protocol of an URI can change the
437 // "nature" of the nsIURL/nsIURI implementation.
439 (void)aURI
->GetSpec(href
);
440 (void)mElement
->SetAttr(kNameSpaceID_None
, nsGkAtoms::href
,
441 NS_ConvertUTF8toUTF16(href
), true);
444 size_t Link::SizeOfExcludingThis(mozilla::SizeOfState
& aState
) const {
447 if (nsCOMPtr
<nsISizeOf
> iface
= do_QueryInterface(mCachedURI
)) {
448 n
+= iface
->SizeOfIncludingThis(aState
.mMallocSizeOf
);
451 // The following members don't need to be measured:
452 // - mElement, because it is a pointer-to-self used to avoid QIs
457 } // namespace mozilla::dom