Bumping manifests a=b2g-bump
[gecko.git] / dom / base / Link.cpp
blob3b2926324ad114275302a2300994fa5b58a38116
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: sw=2 ts=2 et :
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/. */
7 #include "Link.h"
9 #include "mozilla/EventStates.h"
10 #include "mozilla/MemoryReporting.h"
11 #include "mozilla/dom/Element.h"
12 #include "nsIURL.h"
13 #include "nsISizeOf.h"
15 #include "nsEscape.h"
16 #include "nsGkAtoms.h"
17 #include "nsString.h"
18 #include "mozAutoDocUpdate.h"
20 #include "mozilla/Services.h"
22 namespace mozilla {
23 namespace dom {
25 Link::Link(Element *aElement)
26 : mElement(aElement)
27 , mHistory(services::GetHistoryService())
28 , mLinkState(eLinkState_NotLink)
29 , mNeedsRegistration(false)
30 , mRegistered(false)
32 NS_ABORT_IF_FALSE(mElement, "Must have an element");
35 Link::~Link()
37 UnregisterFromHistory();
40 bool
41 Link::ElementHasHref() const
43 return ((!mElement->IsSVG() && mElement->HasAttr(kNameSpaceID_None, nsGkAtoms::href))
44 || (!mElement->IsHTML() && mElement->HasAttr(kNameSpaceID_XLink, nsGkAtoms::href)));
47 void
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.
56 mLinkState = aState;
58 // Per IHistory interface documentation, we are no longer registered.
59 mRegistered = false;
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);
69 EventStates
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();
114 nsIURI*
115 Link::GetURI() const
117 // If we have this URI cached, use it.
118 if (mCachedURI) {
119 return mCachedURI;
122 // Otherwise obtain it.
123 Link *self = const_cast<Link *>(this);
124 Element *element = self->mElement;
125 mCachedURI = element->GetHrefURI();
127 return mCachedURI;
130 void
131 Link::SetProtocol(const nsAString &aProtocol, ErrorResult& aError)
133 nsCOMPtr<nsIURI> uri(GetURIToMutate());
134 if (!uri) {
135 // Ignore failures to be compatible with NS4.
136 return;
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);
149 void
150 Link::SetPassword(const nsAString &aPassword, ErrorResult& aError)
152 nsCOMPtr<nsIURI> uri(GetURIToMutate());
153 if (!uri) {
154 // Ignore failures to be compatible with NS4.
155 return;
158 uri->SetPassword(NS_ConvertUTF16toUTF8(aPassword));
159 SetHrefAttribute(uri);
162 void
163 Link::SetUsername(const nsAString &aUsername, ErrorResult& aError)
165 nsCOMPtr<nsIURI> uri(GetURIToMutate());
166 if (!uri) {
167 // Ignore failures to be compatible with NS4.
168 return;
171 uri->SetUsername(NS_ConvertUTF16toUTF8(aUsername));
172 SetHrefAttribute(uri);
175 void
176 Link::SetHost(const nsAString &aHost, ErrorResult& aError)
178 nsCOMPtr<nsIURI> uri(GetURIToMutate());
179 if (!uri) {
180 // Ignore failures to be compatible with NS4.
181 return;
184 (void)uri->SetHostPort(NS_ConvertUTF16toUTF8(aHost));
185 SetHrefAttribute(uri);
188 void
189 Link::SetHostname(const nsAString &aHostname, ErrorResult& aError)
191 nsCOMPtr<nsIURI> uri(GetURIToMutate());
192 if (!uri) {
193 // Ignore failures to be compatible with NS4.
194 return;
197 (void)uri->SetHost(NS_ConvertUTF16toUTF8(aHostname));
198 SetHrefAttribute(uri);
201 void
202 Link::SetPathname(const nsAString &aPathname, ErrorResult& aError)
204 nsCOMPtr<nsIURI> uri(GetURIToMutate());
205 nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
206 if (!url) {
207 // Ignore failures to be compatible with NS4.
208 return;
211 (void)url->SetFilePath(NS_ConvertUTF16toUTF8(aPathname));
212 SetHrefAttribute(uri);
215 void
216 Link::SetSearch(const nsAString& aSearch, ErrorResult& aError)
218 SetSearchInternal(aSearch);
219 UpdateURLSearchParams();
222 void
223 Link::SetSearchInternal(const nsAString& aSearch)
225 nsCOMPtr<nsIURI> uri(GetURIToMutate());
226 nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
227 if (!url) {
228 // Ignore failures to be compatible with NS4.
229 return;
232 (void)url->SetQuery(NS_ConvertUTF16toUTF8(aSearch));
233 SetHrefAttribute(uri);
236 void
237 Link::SetPort(const nsAString &aPort, ErrorResult& aError)
239 nsCOMPtr<nsIURI> uri(GetURIToMutate());
240 if (!uri) {
241 // Ignore failures to be compatible with NS4.
242 return;
245 nsresult rv;
246 nsAutoString portStr(aPort);
248 // nsIURI uses -1 as default value.
249 int32_t port = -1;
250 if (!aPort.IsEmpty()) {
251 port = portStr.ToInteger(&rv);
252 if (NS_FAILED(rv)) {
253 return;
257 (void)uri->SetPort(port);
258 SetHrefAttribute(uri);
261 void
262 Link::SetHash(const nsAString &aHash, ErrorResult& aError)
264 nsCOMPtr<nsIURI> uri(GetURIToMutate());
265 if (!uri) {
266 // Ignore failures to be compatible with NS4.
267 return;
270 (void)uri->SetRef(NS_ConvertUTF16toUTF8(aHash));
271 SetHrefAttribute(uri);
274 void
275 Link::GetOrigin(nsAString &aOrigin, ErrorResult& aError)
277 aOrigin.Truncate();
279 nsCOMPtr<nsIURI> uri(GetURI());
280 if (!uri) {
281 return;
284 nsString origin;
285 nsContentUtils::GetUTFOrigin(uri, origin);
286 aOrigin.Assign(origin);
289 void
290 Link::GetProtocol(nsAString &_protocol, ErrorResult& aError)
292 nsCOMPtr<nsIURI> uri(GetURI());
293 if (!uri) {
294 _protocol.AssignLiteral("http");
296 else {
297 nsAutoCString scheme;
298 (void)uri->GetScheme(scheme);
299 CopyASCIItoUTF16(scheme, _protocol);
301 _protocol.Append(char16_t(':'));
302 return;
305 void
306 Link::GetUsername(nsAString& aUsername, ErrorResult& aError)
308 aUsername.Truncate();
310 nsCOMPtr<nsIURI> uri(GetURI());
311 if (!uri) {
312 return;
315 nsAutoCString username;
316 uri->GetUsername(username);
317 CopyASCIItoUTF16(username, aUsername);
320 void
321 Link::GetPassword(nsAString &aPassword, ErrorResult& aError)
323 aPassword.Truncate();
325 nsCOMPtr<nsIURI> uri(GetURI());
326 if (!uri) {
327 return;
330 nsAutoCString password;
331 uri->GetPassword(password);
332 CopyASCIItoUTF16(password, aPassword);
335 void
336 Link::GetHost(nsAString &_host, ErrorResult& aError)
338 _host.Truncate();
340 nsCOMPtr<nsIURI> uri(GetURI());
341 if (!uri) {
342 // Do not throw! Not having a valid URI should result in an empty string.
343 return;
346 nsAutoCString hostport;
347 nsresult rv = uri->GetHostPort(hostport);
348 if (NS_SUCCEEDED(rv)) {
349 CopyUTF8toUTF16(hostport, _host);
353 void
354 Link::GetHostname(nsAString &_hostname, ErrorResult& aError)
356 _hostname.Truncate();
358 nsCOMPtr<nsIURI> uri(GetURI());
359 if (!uri) {
360 // Do not throw! Not having a valid URI should result in an empty string.
361 return;
364 nsContentUtils::GetHostOrIPv6WithBrackets(uri, _hostname);
367 void
368 Link::GetPathname(nsAString &_pathname, ErrorResult& aError)
370 _pathname.Truncate();
372 nsCOMPtr<nsIURI> uri(GetURI());
373 nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
374 if (!url) {
375 // Do not throw! Not having a valid URI or URL should result in an empty
376 // string.
377 return;
380 nsAutoCString file;
381 nsresult rv = url->GetFilePath(file);
382 if (NS_SUCCEEDED(rv)) {
383 CopyUTF8toUTF16(file, _pathname);
387 void
388 Link::GetSearch(nsAString &_search, ErrorResult& aError)
390 _search.Truncate();
392 nsCOMPtr<nsIURI> uri(GetURI());
393 nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
394 if (!url) {
395 // Do not throw! Not having a valid URI or URL should result in an empty
396 // string.
397 return;
400 nsAutoCString search;
401 nsresult rv = url->GetQuery(search);
402 if (NS_SUCCEEDED(rv) && !search.IsEmpty()) {
403 CopyUTF8toUTF16(NS_LITERAL_CSTRING("?") + search, _search);
407 void
408 Link::GetPort(nsAString &_port, ErrorResult& aError)
410 _port.Truncate();
412 nsCOMPtr<nsIURI> uri(GetURI());
413 if (!uri) {
414 // Do not throw! Not having a valid URI should result in an empty string.
415 return;
418 int32_t port;
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);
429 void
430 Link::GetHash(nsAString &_hash, ErrorResult& aError)
432 _hash.Truncate();
434 nsCOMPtr<nsIURI> uri(GetURI());
435 if (!uri) {
436 // Do not throw! Not having a valid URI should result in an empty
437 // string.
438 return;
441 nsAutoCString ref;
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);
450 void
451 Link::ResetLinkState(bool aNotify, bool aHasHref)
453 nsLinkState defaultState;
455 // The default state for links with an href is unvisited.
456 if (aHasHref) {
457 defaultState = eLinkState_Unvisited;
458 } else {
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
469 // with it before.
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
493 // required.
494 if (aNotify) {
495 mElement->UpdateState(aNotify);
496 } else {
497 if (mLinkState == eLinkState_Unvisited) {
498 mElement->UpdateLinkState(NS_EVENT_STATE_UNVISITED);
499 } else {
500 mElement->UpdateLinkState(EventStates());
505 void
506 Link::UnregisterFromHistory()
508 // If we are not registered, we have nothing to do.
509 if (!mRegistered) {
510 return;
513 NS_ASSERTION(mCachedURI, "mRegistered is true, but we have no cached URI?!");
515 // And tell History to stop tracking us.
516 if (mHistory) {
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)) {
520 mRegistered = false;
525 already_AddRefed<nsIURI>
526 Link::GetURIToMutate()
528 nsCOMPtr<nsIURI> uri(GetURI());
529 if (!uri) {
530 return nullptr;
532 nsCOMPtr<nsIURI> clone;
533 (void)uri->Clone(getter_AddRefs(clone));
534 return clone.forget();
537 void
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.
545 nsAutoCString href;
546 (void)aURI->GetSpec(href);
547 (void)mElement->SetAttr(kNameSpaceID_None, nsGkAtoms::href,
548 NS_ConvertUTF8toUTF16(href), true);
551 size_t
552 Link::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
554 size_t n = 0;
556 if (mCachedURI) {
557 nsCOMPtr<nsISizeOf> iface = do_QueryInterface(mCachedURI);
558 if (iface) {
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
567 return n;
570 URLSearchParams*
571 Link::SearchParams()
573 CreateSearchParamsIfNeeded();
574 return mSearchParams;
577 void
578 Link::SetSearchParams(URLSearchParams& aSearchParams)
580 if (mSearchParams) {
581 mSearchParams->RemoveObserver(this);
584 mSearchParams = &aSearchParams;
585 mSearchParams->AddObserver(this);
587 nsAutoString search;
588 mSearchParams->Serialize(search);
589 SetSearchInternal(search);
592 void
593 Link::URLSearchParamsUpdated(URLSearchParams* aSearchParams)
595 MOZ_ASSERT(mSearchParams);
596 MOZ_ASSERT(mSearchParams == aSearchParams);
598 nsString search;
599 mSearchParams->Serialize(search);
600 SetSearchInternal(search);
603 void
604 Link::UpdateURLSearchParams()
606 if (!mSearchParams) {
607 return;
610 nsAutoCString search;
611 nsCOMPtr<nsIURI> uri(GetURI());
612 nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
613 if (url) {
614 nsresult rv = url->GetQuery(search);
615 if (NS_FAILED(rv)) {
616 NS_WARNING("Failed to get the query from a nsIURL.");
620 mSearchParams->ParseInput(search, this);
623 void
624 Link::CreateSearchParamsIfNeeded()
626 if (!mSearchParams) {
627 mSearchParams = new URLSearchParams();
628 mSearchParams->AddObserver(this);
629 UpdateURLSearchParams();
633 void
634 Link::Unlink()
636 if (mSearchParams) {
637 mSearchParams->RemoveObserver(this);
638 mSearchParams = nullptr;
642 void
643 Link::Traverse(nsCycleCollectionTraversalCallback &cb)
645 Link* tmp = this;
646 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSearchParams);
649 } // namespace dom
650 } // namespace mozilla