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/EventStates.h"
10 #include "mozilla/MemoryReporting.h"
11 #include "mozilla/dom/Element.h"
12 #include "mozilla/dom/HTMLDNSPrefetch.h"
13 #include "mozilla/IHistory.h"
14 #include "mozilla/StaticPrefs_layout.h"
15 #include "nsLayoutUtils.h"
17 #include "nsIURIMutator.h"
18 #include "nsISizeOf.h"
21 #include "nsGkAtoms.h"
23 #include "mozAutoDocUpdate.h"
25 #include "mozilla/Components.h"
26 #include "nsAttrValueInlines.h"
27 #include "HTMLLinkElement.h"
29 namespace mozilla::dom
{
31 Link::Link(Element
* aElement
)
33 mState(State::NotLink
),
34 mNeedsRegistration(false),
36 mHasPendingLinkUpdate(false),
38 MOZ_ASSERT(mElement
, "Must have an element");
43 mState(State::NotLink
),
44 mNeedsRegistration(false),
46 mHasPendingLinkUpdate(false),
50 // !mElement is for mock_Link.
51 MOZ_ASSERT(!mElement
|| !mElement
->IsInComposedDoc());
52 UnregisterFromHistory();
55 bool Link::ElementHasHref() const {
56 return mElement
->HasAttr(kNameSpaceID_None
, nsGkAtoms::href
) ||
57 (!mElement
->IsHTMLElement() &&
58 mElement
->HasAttr(kNameSpaceID_XLink
, nsGkAtoms::href
));
61 void Link::VisitedQueryFinished(bool aVisited
) {
62 MOZ_ASSERT(mRegistered
, "Setting the link state of an unregistered Link!");
63 MOZ_ASSERT(mState
== State::Unvisited
,
64 "Why would we want to know our visited state otherwise?");
66 auto newState
= aVisited
? State::Visited
: State::Unvisited
;
68 // Set our current state as appropriate.
71 // We will be no longer registered if we're visited, as it'd be pointless, we
72 // never transition from visited -> unvisited.
77 MOZ_ASSERT(LinkState() == NS_EVENT_STATE_VISITED
||
78 LinkState() == NS_EVENT_STATE_UNVISITED
,
79 "Unexpected state obtained from LinkState()!");
81 // Tell the element to update its visited state
82 mElement
->UpdateState(true);
84 if (StaticPrefs::layout_css_always_repaint_on_unvisited()) {
85 // Even if the state didn't actually change, we need to repaint in order for
86 // the visited state not to be observable.
87 nsLayoutUtils::PostRestyleEvent(GetElement(), RestyleHint::RestyleSubtree(),
88 nsChangeHint_RepaintFrame
);
92 EventStates
Link::LinkState() const {
93 // We are a constant method, but we are just lazily doing things and have to
94 // track that state. Cast away that constness!
96 // XXX(emilio): that's evil.
97 Link
* self
= const_cast<Link
*>(this);
99 Element
* element
= self
->mElement
;
101 // If we have not yet registered for notifications and need to,
102 // due to our href changing, register now!
103 if (!mRegistered
&& mNeedsRegistration
&& element
->IsInComposedDoc() &&
104 !HasPendingLinkUpdate()) {
105 // Only try and register once.
106 self
->mNeedsRegistration
= false;
108 nsCOMPtr
<nsIURI
> hrefURI(GetURI());
110 // Assume that we are not visited until we are told otherwise.
111 self
->mState
= State::Unvisited
;
113 // Make sure the href attribute has a valid link (bug 23209).
114 // If we have a good href, register with History if available.
115 if (mHistory
&& hrefURI
) {
116 if (nsCOMPtr
<IHistory
> history
= components::History::Service()) {
117 self
->mRegistered
= true;
118 history
->RegisterVisitedCallback(hrefURI
, self
);
119 // And make sure we are in the document's link map.
120 element
->GetComposedDoc()->AddStyleRelevantLink(self
);
125 // Otherwise, return our known state.
126 if (mState
== State::Visited
) {
127 return NS_EVENT_STATE_VISITED
;
130 if (mState
== State::Unvisited
) {
131 return NS_EVENT_STATE_UNVISITED
;
134 return EventStates();
137 nsIURI
* Link::GetURI() const {
138 // If we have this URI cached, use it.
143 // Otherwise obtain it.
144 Link
* self
= const_cast<Link
*>(this);
145 Element
* element
= self
->mElement
;
146 mCachedURI
= element
->GetHrefURI();
151 void Link::SetProtocol(const nsAString
& aProtocol
) {
152 nsCOMPtr
<nsIURI
> uri(GetURI());
154 // Ignore failures to be compatible with NS4.
158 nsAString::const_iterator start
, end
;
159 aProtocol
.BeginReading(start
);
160 aProtocol
.EndReading(end
);
161 nsAString::const_iterator
iter(start
);
162 (void)FindCharInReadable(':', iter
, end
);
163 nsresult rv
= NS_MutateURI(uri
)
164 .SetScheme(NS_ConvertUTF16toUTF8(Substring(start
, iter
)))
170 SetHrefAttribute(uri
);
173 void Link::SetPassword(const nsAString
& aPassword
) {
174 nsCOMPtr
<nsIURI
> uri(GetURI());
176 // Ignore failures to be compatible with NS4.
180 nsresult rv
= NS_MutateURI(uri
)
181 .SetPassword(NS_ConvertUTF16toUTF8(aPassword
))
183 if (NS_SUCCEEDED(rv
)) {
184 SetHrefAttribute(uri
);
188 void Link::SetUsername(const nsAString
& aUsername
) {
189 nsCOMPtr
<nsIURI
> uri(GetURI());
191 // Ignore failures to be compatible with NS4.
195 nsresult rv
= NS_MutateURI(uri
)
196 .SetUsername(NS_ConvertUTF16toUTF8(aUsername
))
198 if (NS_SUCCEEDED(rv
)) {
199 SetHrefAttribute(uri
);
203 void Link::SetHost(const nsAString
& aHost
) {
204 nsCOMPtr
<nsIURI
> uri(GetURI());
206 // Ignore failures to be compatible with NS4.
211 NS_MutateURI(uri
).SetHostPort(NS_ConvertUTF16toUTF8(aHost
)).Finalize(uri
);
215 SetHrefAttribute(uri
);
218 void Link::SetHostname(const nsAString
& aHostname
) {
219 nsCOMPtr
<nsIURI
> uri(GetURI());
221 // Ignore failures to be compatible with NS4.
226 NS_MutateURI(uri
).SetHost(NS_ConvertUTF16toUTF8(aHostname
)).Finalize(uri
);
230 SetHrefAttribute(uri
);
233 void Link::SetPathname(const nsAString
& aPathname
) {
234 nsCOMPtr
<nsIURI
> uri(GetURI());
235 nsCOMPtr
<nsIURL
> url(do_QueryInterface(uri
));
237 // Ignore failures to be compatible with NS4.
241 nsresult rv
= NS_MutateURI(uri
)
242 .SetFilePath(NS_ConvertUTF16toUTF8(aPathname
))
247 SetHrefAttribute(uri
);
250 void Link::SetSearch(const nsAString
& aSearch
) {
251 nsCOMPtr
<nsIURI
> uri(GetURI());
252 nsCOMPtr
<nsIURL
> url(do_QueryInterface(uri
));
254 // Ignore failures to be compatible with NS4.
258 auto encoding
= mElement
->OwnerDoc()->GetDocumentCharacterSet();
261 .SetQueryWithEncoding(NS_ConvertUTF16toUTF8(aSearch
), encoding
)
266 SetHrefAttribute(uri
);
269 void Link::SetPort(const nsAString
& aPort
) {
270 nsCOMPtr
<nsIURI
> uri(GetURI());
272 // Ignore failures to be compatible with NS4.
277 nsAutoString
portStr(aPort
);
279 // nsIURI uses -1 as default value.
281 if (!aPort
.IsEmpty()) {
282 port
= portStr
.ToInteger(&rv
);
288 rv
= NS_MutateURI(uri
).SetPort(port
).Finalize(uri
);
292 SetHrefAttribute(uri
);
295 void Link::SetHash(const nsAString
& aHash
) {
296 nsCOMPtr
<nsIURI
> uri(GetURI());
298 // Ignore failures to be compatible with NS4.
303 NS_MutateURI(uri
).SetRef(NS_ConvertUTF16toUTF8(aHash
)).Finalize(uri
);
308 SetHrefAttribute(uri
);
311 void Link::GetOrigin(nsAString
& aOrigin
) {
314 nsCOMPtr
<nsIURI
> uri(GetURI());
320 nsContentUtils::GetUTFOrigin(uri
, origin
);
321 aOrigin
.Assign(origin
);
324 void Link::GetProtocol(nsAString
& _protocol
) {
325 nsCOMPtr
<nsIURI
> uri(GetURI());
327 nsAutoCString scheme
;
328 (void)uri
->GetScheme(scheme
);
329 CopyASCIItoUTF16(scheme
, _protocol
);
331 _protocol
.Append(char16_t(':'));
334 void Link::GetUsername(nsAString
& aUsername
) {
335 aUsername
.Truncate();
337 nsCOMPtr
<nsIURI
> uri(GetURI());
342 nsAutoCString username
;
343 uri
->GetUsername(username
);
344 CopyASCIItoUTF16(username
, aUsername
);
347 void Link::GetPassword(nsAString
& aPassword
) {
348 aPassword
.Truncate();
350 nsCOMPtr
<nsIURI
> uri(GetURI());
355 nsAutoCString password
;
356 uri
->GetPassword(password
);
357 CopyASCIItoUTF16(password
, aPassword
);
360 void Link::GetHost(nsAString
& _host
) {
363 nsCOMPtr
<nsIURI
> uri(GetURI());
365 // Do not throw! Not having a valid URI should result in an empty string.
369 nsAutoCString hostport
;
370 nsresult rv
= uri
->GetHostPort(hostport
);
371 if (NS_SUCCEEDED(rv
)) {
372 CopyUTF8toUTF16(hostport
, _host
);
376 void Link::GetHostname(nsAString
& _hostname
) {
377 _hostname
.Truncate();
379 nsCOMPtr
<nsIURI
> uri(GetURI());
381 // Do not throw! Not having a valid URI should result in an empty string.
385 nsContentUtils::GetHostOrIPv6WithBrackets(uri
, _hostname
);
388 void Link::GetPathname(nsAString
& _pathname
) {
389 _pathname
.Truncate();
391 nsCOMPtr
<nsIURI
> uri(GetURI());
392 nsCOMPtr
<nsIURL
> url(do_QueryInterface(uri
));
394 // Do not throw! Not having a valid URI or URL should result in an empty
400 nsresult rv
= url
->GetFilePath(file
);
401 if (NS_SUCCEEDED(rv
)) {
402 CopyUTF8toUTF16(file
, _pathname
);
406 void Link::GetSearch(nsAString
& _search
) {
409 nsCOMPtr
<nsIURI
> uri(GetURI());
410 nsCOMPtr
<nsIURL
> url(do_QueryInterface(uri
));
412 // Do not throw! Not having a valid URI or URL should result in an empty
417 nsAutoCString search
;
418 nsresult rv
= url
->GetQuery(search
);
419 if (NS_SUCCEEDED(rv
) && !search
.IsEmpty()) {
420 _search
.Assign(u
'?');
421 AppendUTF8toUTF16(search
, _search
);
425 void Link::GetPort(nsAString
& _port
) {
428 nsCOMPtr
<nsIURI
> uri(GetURI());
430 // Do not throw! Not having a valid URI should result in an empty string.
435 nsresult rv
= uri
->GetPort(&port
);
436 // Note that failure to get the port from the URI is not necessarily a bad
437 // thing. Some URIs do not have a port.
438 if (NS_SUCCEEDED(rv
) && port
!= -1) {
439 nsAutoString portStr
;
440 portStr
.AppendInt(port
, 10);
441 _port
.Assign(portStr
);
445 void Link::GetHash(nsAString
& _hash
) {
448 nsCOMPtr
<nsIURI
> uri(GetURI());
450 // Do not throw! Not having a valid URI should result in an empty
456 nsresult rv
= uri
->GetRef(ref
);
457 if (NS_SUCCEEDED(rv
) && !ref
.IsEmpty()) {
458 _hash
.Assign(char16_t('#'));
459 AppendUTF8toUTF16(ref
, _hash
);
463 void Link::ResetLinkState(bool aNotify
, bool aHasHref
) {
464 // If !mNeedsRegstration, then either we've never registered, or we're
465 // currently registered; in either case, we should remove ourself
466 // from the doc and the history.
467 if (!mNeedsRegistration
&& mState
!= State::NotLink
) {
468 Document
* doc
= mElement
->GetComposedDoc();
469 if (doc
&& (mRegistered
|| mState
== State::Visited
)) {
470 // Tell the document to forget about this link if we've registered
472 doc
->ForgetLink(this);
476 // If we have an href, we should register with the history.
478 // FIXME(emilio): Do we really want to allow all MathML elements to be
479 // :visited? That seems not great.
480 mNeedsRegistration
= aHasHref
;
482 // If we've cached the URI, reset always invalidates it.
483 UnregisterFromHistory();
484 mCachedURI
= nullptr;
486 // Update our state back to the default; the default state for links with an
487 // href is unvisited.
488 mState
= aHasHref
? State::Unvisited
: State::NotLink
;
490 // We have to be very careful here: if aNotify is false we do NOT
491 // want to call UpdateState, because that will call into LinkState()
492 // and try to start off loads, etc. But ResetLinkState is called
493 // with aNotify false when things are in inconsistent states, so
494 // we'll get confused in that situation. Instead, just silently
495 // update the link state on mElement. Since we might have set the
496 // link state to unvisited, make sure to update with that state if
499 mElement
->UpdateState(aNotify
);
501 if (mState
== State::Unvisited
) {
502 mElement
->UpdateLinkState(NS_EVENT_STATE_UNVISITED
);
504 mElement
->UpdateLinkState(EventStates());
509 void Link::UnregisterFromHistory() {
510 // If we are not registered, we have nothing to do.
515 // And tell History to stop tracking us.
516 if (mHistory
&& mCachedURI
) {
517 if (nsCOMPtr
<IHistory
> history
= components::History::Service()) {
518 history
->UnregisterVisitedCallback(mCachedURI
, this);
524 void Link::SetHrefAttribute(nsIURI
* aURI
) {
525 NS_ASSERTION(aURI
, "Null URI is illegal!");
527 // if we change this code to not reserialize we need to do something smarter
528 // in SetProtocol because changing the protocol of an URI can change the
529 // "nature" of the nsIURL/nsIURI implementation.
531 (void)aURI
->GetSpec(href
);
532 (void)mElement
->SetAttr(kNameSpaceID_None
, nsGkAtoms::href
,
533 NS_ConvertUTF8toUTF16(href
), true);
536 size_t Link::SizeOfExcludingThis(mozilla::SizeOfState
& aState
) const {
539 if (nsCOMPtr
<nsISizeOf
> iface
= do_QueryInterface(mCachedURI
)) {
540 n
+= iface
->SizeOfIncludingThis(aState
.mMallocSizeOf
);
543 // The following members don't need to be measured:
544 // - mElement, because it is a pointer-to-self used to avoid QIs
549 } // namespace mozilla::dom