Bug 1854550 - pt 10. Allow LOG() with zero extra arguments r=glandium
[gecko.git] / dom / base / Link.cpp
blob1f2c381a0a2a8d18b537b2192911799ba9694d8b
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/. */
7 #include "Link.h"
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"
17 #include "nsIURL.h"
18 #include "nsIURIMutator.h"
19 #include "nsISizeOf.h"
21 #include "nsEscape.h"
22 #include "nsGkAtoms.h"
23 #include "nsString.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)
33 : mElement(aElement),
34 mNeedsRegistration(false),
35 mRegistered(false),
36 mHasPendingLinkUpdate(false),
37 mHistory(true) {
38 MOZ_ASSERT(mElement, "Must have an element");
41 Link::Link()
42 : mElement(nullptr),
43 mNeedsRegistration(false),
44 mRegistered(false),
45 mHasPendingLinkUpdate(false),
46 mHistory(false) {}
48 Link::~Link() {
49 // !mElement is for mock_Link.
50 MOZ_ASSERT(!mElement || !mElement->IsInComposedDoc());
51 Unregister();
54 bool Link::ElementHasHref() const {
55 if (mElement->HasAttr(nsGkAtoms::href)) {
56 return true;
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?");
65 return false;
68 void Link::SetLinkState(State aState, bool aNotify) {
69 Element::AutoStateChangeNotifier notifier(*mElement, aNotify);
70 switch (aState) {
71 case State::Visited:
72 mElement->AddStatesSilently(ElementState::VISITED);
73 mElement->RemoveStatesSilently(ElementState::UNVISITED);
74 break;
75 case State::Unvisited:
76 mElement->AddStatesSilently(ElementState::UNVISITED);
77 mElement->RemoveStatesSilently(ElementState::VISITED);
78 break;
79 case State::NotLink:
80 mElement->RemoveStatesSilently(ElementState::VISITED_OR_UNVISITED);
81 break;
85 void Link::TriggerLinkUpdate(bool aNotify) {
86 if (mRegistered || !mNeedsRegistration || mHasPendingLinkUpdate ||
87 !mElement->IsInComposedDoc()) {
88 return;
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()) {
103 mRegistered = true;
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.
124 if (mCachedURI) {
125 return mCachedURI;
128 // Otherwise obtain it.
129 Link* self = const_cast<Link*>(this);
130 Element* element = self->mElement;
131 mCachedURI = element->GetHrefURI();
133 return mCachedURI;
136 void Link::SetProtocol(const nsAString& aProtocol) {
137 nsCOMPtr<nsIURI> uri(GetURI());
138 if (!uri) {
139 // Ignore failures to be compatible with NS4.
140 return;
142 uri = net::TryChangeProtocol(uri, aProtocol);
143 if (!uri) {
144 return;
146 SetHrefAttribute(uri);
149 void Link::SetPassword(const nsAString& aPassword) {
150 nsCOMPtr<nsIURI> uri(GetURI());
151 if (!uri) {
152 // Ignore failures to be compatible with NS4.
153 return;
156 nsresult rv = NS_MutateURI(uri)
157 .SetPassword(NS_ConvertUTF16toUTF8(aPassword))
158 .Finalize(uri);
159 if (NS_SUCCEEDED(rv)) {
160 SetHrefAttribute(uri);
164 void Link::SetUsername(const nsAString& aUsername) {
165 nsCOMPtr<nsIURI> uri(GetURI());
166 if (!uri) {
167 // Ignore failures to be compatible with NS4.
168 return;
171 nsresult rv = NS_MutateURI(uri)
172 .SetUsername(NS_ConvertUTF16toUTF8(aUsername))
173 .Finalize(uri);
174 if (NS_SUCCEEDED(rv)) {
175 SetHrefAttribute(uri);
179 void Link::SetHost(const nsAString& aHost) {
180 nsCOMPtr<nsIURI> uri(GetURI());
181 if (!uri) {
182 // Ignore failures to be compatible with NS4.
183 return;
186 nsresult rv =
187 NS_MutateURI(uri).SetHostPort(NS_ConvertUTF16toUTF8(aHost)).Finalize(uri);
188 if (NS_FAILED(rv)) {
189 return;
191 SetHrefAttribute(uri);
194 void Link::SetHostname(const nsAString& aHostname) {
195 nsCOMPtr<nsIURI> uri(GetURI());
196 if (!uri) {
197 // Ignore failures to be compatible with NS4.
198 return;
201 nsresult rv =
202 NS_MutateURI(uri).SetHost(NS_ConvertUTF16toUTF8(aHostname)).Finalize(uri);
203 if (NS_FAILED(rv)) {
204 return;
206 SetHrefAttribute(uri);
209 void Link::SetPathname(const nsAString& aPathname) {
210 nsCOMPtr<nsIURI> uri(GetURI());
211 if (!uri) {
212 // Ignore failures to be compatible with NS4.
213 return;
216 nsresult rv = NS_MutateURI(uri)
217 .SetFilePath(NS_ConvertUTF16toUTF8(aPathname))
218 .Finalize(uri);
219 if (NS_FAILED(rv)) {
220 return;
222 SetHrefAttribute(uri);
225 void Link::SetSearch(const nsAString& aSearch) {
226 nsCOMPtr<nsIURI> uri(GetURI());
227 if (!uri) {
228 // Ignore failures to be compatible with NS4.
229 return;
232 auto encoding = mElement->OwnerDoc()->GetDocumentCharacterSet();
233 nsresult rv =
234 NS_MutateURI(uri)
235 .SetQueryWithEncoding(NS_ConvertUTF16toUTF8(aSearch), encoding)
236 .Finalize(uri);
237 if (NS_FAILED(rv)) {
238 return;
240 SetHrefAttribute(uri);
243 void Link::SetPort(const nsAString& aPort) {
244 nsCOMPtr<nsIURI> uri(GetURI());
245 if (!uri) {
246 // Ignore failures to be compatible with NS4.
247 return;
250 nsresult rv;
251 nsAutoString portStr(aPort);
253 // nsIURI uses -1 as default value.
254 int32_t port = -1;
255 if (!aPort.IsEmpty()) {
256 port = portStr.ToInteger(&rv);
257 if (NS_FAILED(rv)) {
258 return;
262 rv = NS_MutateURI(uri).SetPort(port).Finalize(uri);
263 if (NS_FAILED(rv)) {
264 return;
266 SetHrefAttribute(uri);
269 void Link::SetHash(const nsAString& aHash) {
270 nsCOMPtr<nsIURI> uri(GetURI());
271 if (!uri) {
272 // Ignore failures to be compatible with NS4.
273 return;
276 nsresult rv =
277 NS_MutateURI(uri).SetRef(NS_ConvertUTF16toUTF8(aHash)).Finalize(uri);
278 if (NS_FAILED(rv)) {
279 return;
282 SetHrefAttribute(uri);
285 void Link::GetOrigin(nsAString& aOrigin) {
286 aOrigin.Truncate();
288 nsCOMPtr<nsIURI> uri(GetURI());
289 if (!uri) {
290 return;
293 nsString origin;
294 nsContentUtils::GetWebExposedOriginSerialization(uri, origin);
295 aOrigin.Assign(origin);
298 void Link::GetProtocol(nsAString& _protocol) {
299 nsCOMPtr<nsIURI> uri(GetURI());
300 if (uri) {
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());
312 if (!uri) {
313 return;
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());
325 if (!uri) {
326 return;
329 nsAutoCString password;
330 uri->GetPassword(password);
331 CopyASCIItoUTF16(password, aPassword);
334 void Link::GetHost(nsAString& _host) {
335 _host.Truncate();
337 nsCOMPtr<nsIURI> uri(GetURI());
338 if (!uri) {
339 // Do not throw! Not having a valid URI should result in an empty string.
340 return;
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());
354 if (!uri) {
355 // Do not throw! Not having a valid URI should result in an empty string.
356 return;
359 nsContentUtils::GetHostOrIPv6WithBrackets(uri, _hostname);
362 void Link::GetPathname(nsAString& _pathname) {
363 _pathname.Truncate();
365 nsCOMPtr<nsIURI> uri(GetURI());
366 if (!uri) {
367 // Do not throw! Not having a valid URI should result in an empty string.
368 return;
371 nsAutoCString file;
372 nsresult rv = uri->GetFilePath(file);
373 if (NS_SUCCEEDED(rv)) {
374 CopyUTF8toUTF16(file, _pathname);
378 void Link::GetSearch(nsAString& _search) {
379 _search.Truncate();
381 nsCOMPtr<nsIURI> uri(GetURI());
382 if (!uri) {
383 // Do not throw! Not having a valid URI or URL should result in an empty
384 // string.
385 return;
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) {
397 _port.Truncate();
399 nsCOMPtr<nsIURI> uri(GetURI());
400 if (!uri) {
401 // Do not throw! Not having a valid URI should result in an empty string.
402 return;
405 int32_t port;
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) {
417 _hash.Truncate();
419 nsCOMPtr<nsIURI> uri(GetURI());
420 if (!uri) {
421 // Do not throw! Not having a valid URI should result in an empty
422 // string.
423 return;
426 nsAutoCString ref;
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.
449 Unregister();
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.
460 if (!mRegistered) {
461 return;
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);
472 mRegistered = false;
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.
481 nsAutoCString href;
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 {
488 size_t n = 0;
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
497 return n;
500 } // namespace mozilla::dom