1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
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"
13 #include "nsISizeOf.h"
16 #include "nsGkAtoms.h"
18 #include "mozAutoDocUpdate.h"
20 #include "mozilla/Services.h"
25 Link::Link(Element
*aElement
)
27 , mHistory(services::GetHistoryService())
28 , mLinkState(eLinkState_NotLink
)
29 , mNeedsRegistration(false)
32 NS_ABORT_IF_FALSE(mElement
, "Must have an element");
37 UnregisterFromHistory();
41 Link::ElementHasHref() const
43 return ((!mElement
->IsSVG() && mElement
->HasAttr(kNameSpaceID_None
, nsGkAtoms::href
))
44 || (!mElement
->IsHTML() && mElement
->HasAttr(kNameSpaceID_XLink
, nsGkAtoms::href
)));
48 Link::SetLinkState(nsLinkState aState
)
50 NS_ASSERTION(mRegistered
,
51 "Setting the link state of an unregistered Link!");
52 NS_ASSERTION(mLinkState
!= aState
,
53 "Setting state to the currently set state!");
55 // Set our current state as appropriate.
58 // Per IHistory interface documentation, we are no longer registered.
61 NS_ABORT_IF_FALSE(LinkState() == NS_EVENT_STATE_VISITED
||
62 LinkState() == NS_EVENT_STATE_UNVISITED
,
63 "Unexpected state obtained from LinkState()!");
65 // Tell the element to update its visited state
66 mElement
->UpdateState(true);
70 Link::LinkState() const
72 // We are a constant method, but we are just lazily doing things and have to
73 // track that state. Cast away that constness!
74 Link
*self
= const_cast<Link
*>(this);
76 Element
*element
= self
->mElement
;
78 // If we have not yet registered for notifications and need to,
79 // due to our href changing, register now!
80 if (!mRegistered
&& mNeedsRegistration
&& element
->IsInComposedDoc()) {
81 // Only try and register once.
82 self
->mNeedsRegistration
= false;
84 nsCOMPtr
<nsIURI
> hrefURI(GetURI());
86 // Assume that we are not visited until we are told otherwise.
87 self
->mLinkState
= eLinkState_Unvisited
;
89 // Make sure the href attribute has a valid link (bug 23209).
90 // If we have a good href, register with History if available.
91 if (mHistory
&& hrefURI
) {
92 nsresult rv
= mHistory
->RegisterVisitedCallback(hrefURI
, self
);
93 if (NS_SUCCEEDED(rv
)) {
94 self
->mRegistered
= true;
96 // And make sure we are in the document's link map.
97 element
->GetComposedDoc()->AddStyleRelevantLink(self
);
102 // Otherwise, return our known state.
103 if (mLinkState
== eLinkState_Visited
) {
104 return NS_EVENT_STATE_VISITED
;
107 if (mLinkState
== eLinkState_Unvisited
) {
108 return NS_EVENT_STATE_UNVISITED
;
111 return EventStates();
117 // If we have this URI cached, use it.
122 // Otherwise obtain it.
123 Link
*self
= const_cast<Link
*>(this);
124 Element
*element
= self
->mElement
;
125 mCachedURI
= element
->GetHrefURI();
131 Link::SetProtocol(const nsAString
&aProtocol
, ErrorResult
& aError
)
133 nsCOMPtr
<nsIURI
> uri(GetURIToMutate());
135 // Ignore failures to be compatible with NS4.
139 nsAString::const_iterator start
, end
;
140 aProtocol
.BeginReading(start
);
141 aProtocol
.EndReading(end
);
142 nsAString::const_iterator
iter(start
);
143 (void)FindCharInReadable(':', iter
, end
);
144 (void)uri
->SetScheme(NS_ConvertUTF16toUTF8(Substring(start
, iter
)));
146 SetHrefAttribute(uri
);
150 Link::SetPassword(const nsAString
&aPassword
, ErrorResult
& aError
)
152 nsCOMPtr
<nsIURI
> uri(GetURIToMutate());
154 // Ignore failures to be compatible with NS4.
158 uri
->SetPassword(NS_ConvertUTF16toUTF8(aPassword
));
159 SetHrefAttribute(uri
);
163 Link::SetUsername(const nsAString
&aUsername
, ErrorResult
& aError
)
165 nsCOMPtr
<nsIURI
> uri(GetURIToMutate());
167 // Ignore failures to be compatible with NS4.
171 uri
->SetUsername(NS_ConvertUTF16toUTF8(aUsername
));
172 SetHrefAttribute(uri
);
176 Link::SetHost(const nsAString
&aHost
, ErrorResult
& aError
)
178 nsCOMPtr
<nsIURI
> uri(GetURIToMutate());
180 // Ignore failures to be compatible with NS4.
184 (void)uri
->SetHostPort(NS_ConvertUTF16toUTF8(aHost
));
185 SetHrefAttribute(uri
);
189 Link::SetHostname(const nsAString
&aHostname
, ErrorResult
& aError
)
191 nsCOMPtr
<nsIURI
> uri(GetURIToMutate());
193 // Ignore failures to be compatible with NS4.
197 (void)uri
->SetHost(NS_ConvertUTF16toUTF8(aHostname
));
198 SetHrefAttribute(uri
);
202 Link::SetPathname(const nsAString
&aPathname
, ErrorResult
& aError
)
204 nsCOMPtr
<nsIURI
> uri(GetURIToMutate());
205 nsCOMPtr
<nsIURL
> url(do_QueryInterface(uri
));
207 // Ignore failures to be compatible with NS4.
211 (void)url
->SetFilePath(NS_ConvertUTF16toUTF8(aPathname
));
212 SetHrefAttribute(uri
);
216 Link::SetSearch(const nsAString
& aSearch
, ErrorResult
& aError
)
218 SetSearchInternal(aSearch
);
219 UpdateURLSearchParams();
223 Link::SetSearchInternal(const nsAString
& aSearch
)
225 nsCOMPtr
<nsIURI
> uri(GetURIToMutate());
226 nsCOMPtr
<nsIURL
> url(do_QueryInterface(uri
));
228 // Ignore failures to be compatible with NS4.
232 (void)url
->SetQuery(NS_ConvertUTF16toUTF8(aSearch
));
233 SetHrefAttribute(uri
);
237 Link::SetPort(const nsAString
&aPort
, ErrorResult
& aError
)
239 nsCOMPtr
<nsIURI
> uri(GetURIToMutate());
241 // Ignore failures to be compatible with NS4.
246 nsAutoString
portStr(aPort
);
248 // nsIURI uses -1 as default value.
250 if (!aPort
.IsEmpty()) {
251 port
= portStr
.ToInteger(&rv
);
257 (void)uri
->SetPort(port
);
258 SetHrefAttribute(uri
);
262 Link::SetHash(const nsAString
&aHash
, ErrorResult
& aError
)
264 nsCOMPtr
<nsIURI
> uri(GetURIToMutate());
266 // Ignore failures to be compatible with NS4.
270 (void)uri
->SetRef(NS_ConvertUTF16toUTF8(aHash
));
271 SetHrefAttribute(uri
);
275 Link::GetOrigin(nsAString
&aOrigin
, ErrorResult
& aError
)
279 nsCOMPtr
<nsIURI
> uri(GetURI());
285 nsContentUtils::GetUTFOrigin(uri
, origin
);
286 aOrigin
.Assign(origin
);
290 Link::GetProtocol(nsAString
&_protocol
, ErrorResult
& aError
)
292 nsCOMPtr
<nsIURI
> uri(GetURI());
294 _protocol
.AssignLiteral("http");
297 nsAutoCString scheme
;
298 (void)uri
->GetScheme(scheme
);
299 CopyASCIItoUTF16(scheme
, _protocol
);
301 _protocol
.Append(char16_t(':'));
306 Link::GetUsername(nsAString
& aUsername
, ErrorResult
& aError
)
308 aUsername
.Truncate();
310 nsCOMPtr
<nsIURI
> uri(GetURI());
315 nsAutoCString username
;
316 uri
->GetUsername(username
);
317 CopyASCIItoUTF16(username
, aUsername
);
321 Link::GetPassword(nsAString
&aPassword
, ErrorResult
& aError
)
323 aPassword
.Truncate();
325 nsCOMPtr
<nsIURI
> uri(GetURI());
330 nsAutoCString password
;
331 uri
->GetPassword(password
);
332 CopyASCIItoUTF16(password
, aPassword
);
336 Link::GetHost(nsAString
&_host
, ErrorResult
& aError
)
340 nsCOMPtr
<nsIURI
> uri(GetURI());
342 // Do not throw! Not having a valid URI should result in an empty string.
346 nsAutoCString hostport
;
347 nsresult rv
= uri
->GetHostPort(hostport
);
348 if (NS_SUCCEEDED(rv
)) {
349 CopyUTF8toUTF16(hostport
, _host
);
354 Link::GetHostname(nsAString
&_hostname
, ErrorResult
& aError
)
356 _hostname
.Truncate();
358 nsCOMPtr
<nsIURI
> uri(GetURI());
360 // Do not throw! Not having a valid URI should result in an empty string.
364 nsContentUtils::GetHostOrIPv6WithBrackets(uri
, _hostname
);
368 Link::GetPathname(nsAString
&_pathname
, ErrorResult
& aError
)
370 _pathname
.Truncate();
372 nsCOMPtr
<nsIURI
> uri(GetURI());
373 nsCOMPtr
<nsIURL
> url(do_QueryInterface(uri
));
375 // Do not throw! Not having a valid URI or URL should result in an empty
381 nsresult rv
= url
->GetFilePath(file
);
382 if (NS_SUCCEEDED(rv
)) {
383 CopyUTF8toUTF16(file
, _pathname
);
388 Link::GetSearch(nsAString
&_search
, ErrorResult
& aError
)
392 nsCOMPtr
<nsIURI
> uri(GetURI());
393 nsCOMPtr
<nsIURL
> url(do_QueryInterface(uri
));
395 // Do not throw! Not having a valid URI or URL should result in an empty
400 nsAutoCString search
;
401 nsresult rv
= url
->GetQuery(search
);
402 if (NS_SUCCEEDED(rv
) && !search
.IsEmpty()) {
403 CopyUTF8toUTF16(NS_LITERAL_CSTRING("?") + search
, _search
);
408 Link::GetPort(nsAString
&_port
, ErrorResult
& aError
)
412 nsCOMPtr
<nsIURI
> uri(GetURI());
414 // Do not throw! Not having a valid URI should result in an empty string.
419 nsresult rv
= uri
->GetPort(&port
);
420 // Note that failure to get the port from the URI is not necessarily a bad
421 // thing. Some URIs do not have a port.
422 if (NS_SUCCEEDED(rv
) && port
!= -1) {
423 nsAutoString portStr
;
424 portStr
.AppendInt(port
, 10);
425 _port
.Assign(portStr
);
430 Link::GetHash(nsAString
&_hash
, ErrorResult
& aError
)
434 nsCOMPtr
<nsIURI
> uri(GetURI());
436 // Do not throw! Not having a valid URI should result in an empty
442 nsresult rv
= uri
->GetRef(ref
);
443 if (NS_SUCCEEDED(rv
) && !ref
.IsEmpty()) {
444 NS_UnescapeURL(ref
); // XXX may result in random non-ASCII bytes!
445 _hash
.Assign(char16_t('#'));
446 AppendUTF8toUTF16(ref
, _hash
);
451 Link::ResetLinkState(bool aNotify
, bool aHasHref
)
453 nsLinkState defaultState
;
455 // The default state for links with an href is unvisited.
457 defaultState
= eLinkState_Unvisited
;
459 defaultState
= eLinkState_NotLink
;
462 // If !mNeedsRegstration, then either we've never registered, or we're
463 // currently registered; in either case, we should remove ourself
464 // from the doc and the history.
465 if (!mNeedsRegistration
&& mLinkState
!= eLinkState_NotLink
) {
466 nsIDocument
*doc
= mElement
->GetComposedDoc();
467 if (doc
&& (mRegistered
|| mLinkState
== eLinkState_Visited
)) {
468 // Tell the document to forget about this link if we've registered
470 doc
->ForgetLink(this);
473 UnregisterFromHistory();
476 // If we have an href, we should register with the history.
477 mNeedsRegistration
= aHasHref
;
479 // If we've cached the URI, reset always invalidates it.
480 mCachedURI
= nullptr;
481 UpdateURLSearchParams();
483 // Update our state back to the default.
484 mLinkState
= defaultState
;
486 // We have to be very careful here: if aNotify is false we do NOT
487 // want to call UpdateState, because that will call into LinkState()
488 // and try to start off loads, etc. But ResetLinkState is called
489 // with aNotify false when things are in inconsistent states, so
490 // we'll get confused in that situation. Instead, just silently
491 // update the link state on mElement. Since we might have set the
492 // link state to unvisited, make sure to update with that state if
495 mElement
->UpdateState(aNotify
);
497 if (mLinkState
== eLinkState_Unvisited
) {
498 mElement
->UpdateLinkState(NS_EVENT_STATE_UNVISITED
);
500 mElement
->UpdateLinkState(EventStates());
506 Link::UnregisterFromHistory()
508 // If we are not registered, we have nothing to do.
513 NS_ASSERTION(mCachedURI
, "mRegistered is true, but we have no cached URI?!");
515 // And tell History to stop tracking us.
517 nsresult rv
= mHistory
->UnregisterVisitedCallback(mCachedURI
, this);
518 NS_ASSERTION(NS_SUCCEEDED(rv
), "This should only fail if we misuse the API!");
519 if (NS_SUCCEEDED(rv
)) {
525 already_AddRefed
<nsIURI
>
526 Link::GetURIToMutate()
528 nsCOMPtr
<nsIURI
> uri(GetURI());
532 nsCOMPtr
<nsIURI
> clone
;
533 (void)uri
->Clone(getter_AddRefs(clone
));
534 return clone
.forget();
538 Link::SetHrefAttribute(nsIURI
*aURI
)
540 NS_ASSERTION(aURI
, "Null URI is illegal!");
542 // if we change this code to not reserialize we need to do something smarter
543 // in SetProtocol because changing the protocol of an URI can change the
544 // "nature" of the nsIURL/nsIURI implementation.
546 (void)aURI
->GetSpec(href
);
547 (void)mElement
->SetAttr(kNameSpaceID_None
, nsGkAtoms::href
,
548 NS_ConvertUTF8toUTF16(href
), true);
552 Link::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf
) const
557 nsCOMPtr
<nsISizeOf
> iface
= do_QueryInterface(mCachedURI
);
559 n
+= iface
->SizeOfIncludingThis(aMallocSizeOf
);
563 // The following members don't need to be measured:
564 // - mElement, because it is a pointer-to-self used to avoid QIs
565 // - mHistory, because it is non-owning
573 CreateSearchParamsIfNeeded();
574 return mSearchParams
;
578 Link::SetSearchParams(URLSearchParams
& aSearchParams
)
581 mSearchParams
->RemoveObserver(this);
584 mSearchParams
= &aSearchParams
;
585 mSearchParams
->AddObserver(this);
588 mSearchParams
->Serialize(search
);
589 SetSearchInternal(search
);
593 Link::URLSearchParamsUpdated(URLSearchParams
* aSearchParams
)
595 MOZ_ASSERT(mSearchParams
);
596 MOZ_ASSERT(mSearchParams
== aSearchParams
);
599 mSearchParams
->Serialize(search
);
600 SetSearchInternal(search
);
604 Link::UpdateURLSearchParams()
606 if (!mSearchParams
) {
610 nsAutoCString search
;
611 nsCOMPtr
<nsIURI
> uri(GetURI());
612 nsCOMPtr
<nsIURL
> url(do_QueryInterface(uri
));
614 nsresult rv
= url
->GetQuery(search
);
616 NS_WARNING("Failed to get the query from a nsIURL.");
620 mSearchParams
->ParseInput(search
, this);
624 Link::CreateSearchParamsIfNeeded()
626 if (!mSearchParams
) {
627 mSearchParams
= new URLSearchParams();
628 mSearchParams
->AddObserver(this);
629 UpdateURLSearchParams();
637 mSearchParams
->RemoveObserver(this);
638 mSearchParams
= nullptr;
643 Link::Traverse(nsCycleCollectionTraversalCallback
&cb
)
646 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSearchParams
);
650 } // namespace mozilla